diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index eba70ff..34efddf 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -9,6 +9,7 @@ return (new PhpCsFixer\Config()) ->setRules( [ + '@PSR2' => true, 'no_unused_imports' => true, 'ordered_imports' => [ 'sort_algorithm' => 'alpha', diff --git a/Dockerfile b/Dockerfile index 58b765b..59c571f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ARG PHP_VERSION=7.4 FROM php:${PHP_VERSION}-cli-alpine -ARG XDEBUG_VERSION=2.9.8 +ARG XDEBUG_VERSION=3.1.1 COPY --from=composer /usr/bin/composer /usr/bin/composer RUN composer --version diff --git a/README.md b/README.md index 975ec71..5e3ba3c 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,10 @@ major version upgrades will have incompatibilities that will be released in the 'registry.example.com']) ); @@ -140,12 +140,12 @@ $schemaId = $registry->schemaId('test-subject', $schema)->wait(); ```php 'registry.example.com']) ) ); @@ -173,21 +173,21 @@ It supports both async and sync APIs. ```php 'registry.example.com']) ); -$syncApi = new BlockingRegistry($asyncApi); +$syncApi = new BlockingDecorator($asyncApi); -$doctrineCachedSyncApi = new CachedRegistry( +$doctrineCachedSyncApi = new CachingDecorator( $asyncApi, new DoctrineCacheAdapter( new ArrayCache() @@ -195,8 +195,8 @@ $doctrineCachedSyncApi = new CachedRegistry( ); // All adapters support both APIs, for async APIs additional fulfillment callbacks will be registered. -$avroObjectCachedAsyncApi = new CachedRegistry( - $syncApi, +$avroObjectCachedAsyncApi = new CachingDecorator( + $asyncApi, new AvroObjectCacheAdapter() ); @@ -212,7 +212,7 @@ $sha1HashFunction = static function (AvroSchema $schema) { }; // Pass the hash function as optional 3rd parameter to the CachedRegistry constructor -$avroObjectCachedAsyncApi = new CachedRegistry( +$avroObjectCachedAsyncApi = new CachingDecorator( $syncApi, new AvroObjectCacheAdapter(), $sha1HashFunction diff --git a/composer.json b/composer.json index e25dd84..321702b 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "php": "^7.4|^8.0|8.1.*", "ext-curl": "*", "ext-json": "*", + "psr/http-client": "~1.0", "guzzlehttp/guzzle": "^7.0", "guzzlehttp/promises": "^1.4.0", "guzzlehttp/psr7": "^1.7", diff --git a/src/AsynchronousRegistry.php b/src/AsynchronousRegistry.php index 0bf0a71..d307f3a 100644 --- a/src/AsynchronousRegistry.php +++ b/src/AsynchronousRegistry.php @@ -8,9 +8,6 @@ use FlixTech\SchemaRegistryApi\Schema\AvroReference; use GuzzleHttp\Promise\PromiseInterface; -/** - * {@inheritdoc} - */ interface AsynchronousRegistry extends Registry { /** diff --git a/src/Constants.php b/src/Constants.php new file mode 100644 index 0000000..a9842bb --- /dev/null +++ b/src/Constants.php @@ -0,0 +1,32 @@ + 'application/vnd.schemaregistry.v1+json']; + public const CONTENT_TYPE_HEADER = [self::CONTENT_TYPE => 'application/vnd.schemaregistry.v1+json']; + public const CONTENT_TYPE = 'Content-Type'; + public const AVRO_TYPE = 'AVRO'; + public const JSON_TYPE = 'JSON'; + public const PROTOBUF_TYPE = 'PROTOBUF'; + + private function __construct() + { + } + + private function __clone() + { + } +} diff --git a/src/Constants/Constants.php b/src/Constants/Constants.php index 5dd2e99..c6ae24c 100644 --- a/src/Constants/Constants.php +++ b/src/Constants/Constants.php @@ -2,15 +2,62 @@ namespace FlixTech\SchemaRegistryApi\Constants; +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::COMPATIBILITY_NONE instead + */ const COMPATIBILITY_NONE = 'NONE'; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::COMPATIBILITY_BACKWARD instead + */ const COMPATIBILITY_BACKWARD = 'BACKWARD'; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::COMPATIBILITY_BACKWARD instead + */ const COMPATIBILITY_BACKWARD_TRANSITIVE = 'BACKWARD_TRANSITIVE'; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::COMPATIBILITY_FORWARD instead + */ const COMPATIBILITY_FORWARD = 'FORWARD'; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::COMPATIBILITY_FORWARD_TRANSITIVE instead + */ const COMPATIBILITY_FORWARD_TRANSITIVE = 'FORWARD_TRANSITIVE'; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::COMPATIBILITY_FULL instead + */ const COMPATIBILITY_FULL = 'FULL'; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::COMPATIBILITY_FULL_TRANSITIVE instead + */ const COMPATIBILITY_FULL_TRANSITIVE = 'FULL_TRANSITIVE'; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::VERSION_LATEST instead + */ const VERSION_LATEST = 'latest'; -const ACCEPT_HEADER_KEY = 'Accept'; -const ACCEPT_HEADER = [ACCEPT_HEADER_KEY => 'application/vnd.schemaregistry.v1+json']; -const CONTENT_TYPE_HEADER_KEY = 'Content-Type'; -const CONTENT_TYPE_HEADER = [CONTENT_TYPE_HEADER_KEY => 'application/vnd.schemaregistry.v1+json']; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::ACCEPT instead + */ +const ACCEPT = 'Accept'; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::ACCEPT_HEADER instead + */ +const ACCEPT_HEADER = [ACCEPT => 'application/vnd.schemaregistry.v1+json']; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::CONTENT_TYPE instead + */ +const CONTENT_TYPE = 'Content-Type'; + +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Constants::CONTENT_TYPE_HEADER instead + */ +const CONTENT_TYPE_HEADER = [CONTENT_TYPE => 'application/vnd.schemaregistry.v1+json']; diff --git a/src/Exception/AbstractSchemaRegistryException.php b/src/Exception/AbstractSchemaRegistryException.php index e94c049..aa922c0 100644 --- a/src/Exception/AbstractSchemaRegistryException.php +++ b/src/Exception/AbstractSchemaRegistryException.php @@ -4,10 +4,9 @@ namespace FlixTech\SchemaRegistryApi\Exception; -use LogicException; -use RuntimeException; +use RuntimeException as PHPRuntimeException; -abstract class AbstractSchemaRegistryException extends RuntimeException implements SchemaRegistryException +abstract class AbstractSchemaRegistryException extends PHPRuntimeException implements SchemaRegistryException { public const ERROR_CODE = 0; diff --git a/src/Exception/ExceptionMap.php b/src/Exception/ExceptionMap.php index 455f8c8..6a40097 100644 --- a/src/Exception/ExceptionMap.php +++ b/src/Exception/ExceptionMap.php @@ -4,10 +4,9 @@ namespace FlixTech\SchemaRegistryApi\Exception; -use Exception; +use FlixTech\SchemaRegistryApi\Json; use GuzzleHttp\Exception\RequestException; use Psr\Http\Message\ResponseInterface; -use RuntimeException; use function array_key_exists; use function sprintf; @@ -38,60 +37,35 @@ private function __construct() /** * Maps a RequestException to the internal SchemaRegistryException types. * - * @param RequestException $exception + * @param ResponseInterface $response * * @return SchemaRegistryException - * - * @throws RuntimeException */ - public function __invoke(RequestException $exception): SchemaRegistryException + public function __invoke(ResponseInterface $response): SchemaRegistryException { - $response = $this->guardAgainstMissingResponse($exception); - $decodedBody = $this->guardAgainstMissingErrorCode($response); + $this->guardAgainstValidHTPPCode($response); + + $decodedBody = Json::decodeResponse($response); + $this->guardAgainstMissingErrorCode($decodedBody); $errorCode = $decodedBody[self::ERROR_CODE_FIELD_NAME]; $errorMessage = $decodedBody[self::ERROR_MESSAGE_FIELD_NAME]; return $this->mapErrorCodeToException($errorCode, $errorMessage); } - private function guardAgainstMissingResponse(RequestException $exception): ResponseInterface + public function isHttpError(ResponseInterface $response): bool { - $response = $exception->getResponse(); - - if (!$response) { - throw new RuntimeException('RequestException has no response to inspect', 0, $exception); - } - - return $response; + return $response->getStatusCode() >= 400 && $response->getStatusCode() < 600; } /** - * @param ResponseInterface $response - * @return array + * @param array $decodedBody */ - private function guardAgainstMissingErrorCode(ResponseInterface $response): array + private function guardAgainstMissingErrorCode(array $decodedBody): void { - try { - $decodedBody = \GuzzleHttp\json_decode((string) $response->getBody(), true); - - if (!is_array($decodedBody) || !array_key_exists(self::ERROR_CODE_FIELD_NAME, $decodedBody)) { - throw new RuntimeException( - sprintf( - 'Invalid message body received - cannot find "error_code" field in response body "%s"', - (string) $response->getBody() - ) - ); - } - - return $decodedBody; - } catch (Exception $e) { + if (!is_array($decodedBody) || !array_key_exists(self::ERROR_CODE_FIELD_NAME, $decodedBody)) { throw new RuntimeException( - sprintf( - 'Invalid message body received - cannot find "error_code" field in response body "%s"', - (string) $response->getBody() - ), - $e->getCode(), - $e + 'Invalid message body received - cannot find "error_code" field in response body.' ); } } @@ -133,4 +107,13 @@ private function mapErrorCodeToException(int $errorCode, string $errorMessage): throw new RuntimeException(sprintf('Unknown error code "%d"', $errorCode)); } } + + private function guardAgainstValidHTPPCode(ResponseInterface $response): void + { + if (!$this->isHttpError($response)) { + throw new RuntimeException( + sprintf('Cannot process response without invalid HTTP code %d', $response->getStatusCode()), + ); + } + } } diff --git a/src/Exception/LogicException.php b/src/Exception/LogicException.php new file mode 100644 index 0000000..60b151a --- /dev/null +++ b/src/Exception/LogicException.php @@ -0,0 +1,17 @@ +isJsonString('$schema must be a valid JSON string'); + + return $schema; + } + + /** + * @param string $jsonString + * @param int<1, max> $depth + * + * @return mixed|array + * + * @throws JsonException + */ + public static function decode(string $jsonString, int $depth = 512) + { + return json_decode($jsonString, true, $depth, JSON_THROW_ON_ERROR); + } + + /** + * @param mixed $data + * + * @return string + * + * @throws JsonException + */ + public static function encode($data): string + { + return json_encode($data, JSON_THROW_ON_ERROR); + } + + /** + * @param ResponseInterface $response + * + * @return array + */ + public static function decodeResponse(ResponseInterface $response): array + { + $body = (string)$response->getBody(); + + try { + return Json::decode($body); + } catch (JsonException $e) { + throw new InvalidArgumentException( + sprintf('%s - with content "%s"', $e->getMessage(), $body), + $e->getCode(), + $e + ); + } + } + + private function __clone() + { + } +} diff --git a/src/Registry/Cache/AvroObjectCacheAdapter.php b/src/Registry/Cache/AvroObjectCacheAdapter.php index cdaf262..4c813db 100644 --- a/src/Registry/Cache/AvroObjectCacheAdapter.php +++ b/src/Registry/Cache/AvroObjectCacheAdapter.php @@ -5,11 +5,7 @@ namespace FlixTech\SchemaRegistryApi\Registry\Cache; use AvroSchema; -use FlixTech\SchemaRegistryApi\Registry\CacheAdapter; -/** - * {@inheritdoc} - */ class AvroObjectCacheAdapter implements CacheAdapter { /** diff --git a/src/Registry/CacheAdapter.php b/src/Registry/Cache/CacheAdapter.php similarity index 98% rename from src/Registry/CacheAdapter.php rename to src/Registry/Cache/CacheAdapter.php index 8abf7aa..0e4f27c 100644 --- a/src/Registry/CacheAdapter.php +++ b/src/Registry/Cache/CacheAdapter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace FlixTech\SchemaRegistryApi\Registry; +namespace FlixTech\SchemaRegistryApi\Registry\Cache; use AvroSchema; diff --git a/src/Registry/Cache/CacheItemPoolAdapter.php b/src/Registry/Cache/CacheItemPoolAdapter.php index 194fcac..872c008 100644 --- a/src/Registry/Cache/CacheItemPoolAdapter.php +++ b/src/Registry/Cache/CacheItemPoolAdapter.php @@ -6,7 +6,6 @@ use AvroSchema; use AvroSchemaParseException; -use FlixTech\SchemaRegistryApi\Registry\CacheAdapter; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\Exception\InvalidArgumentException; diff --git a/src/Registry/Cache/DoctrineCacheAdapter.php b/src/Registry/Cache/DoctrineCacheAdapter.php index 557b049..904fe77 100644 --- a/src/Registry/Cache/DoctrineCacheAdapter.php +++ b/src/Registry/Cache/DoctrineCacheAdapter.php @@ -7,11 +7,7 @@ use AvroSchema; use AvroSchemaParseException; use Doctrine\Common\Cache\Cache; -use FlixTech\SchemaRegistryApi\Registry\CacheAdapter; -/** - * {@inheritdoc} - */ class DoctrineCacheAdapter implements CacheAdapter { /** diff --git a/src/Registry/Cache/SimpleCacheAdapter.php b/src/Registry/Cache/SimpleCacheAdapter.php index 0a8381d..a81024f 100644 --- a/src/Registry/Cache/SimpleCacheAdapter.php +++ b/src/Registry/Cache/SimpleCacheAdapter.php @@ -6,7 +6,6 @@ use AvroSchema; use AvroSchemaParseException; -use FlixTech\SchemaRegistryApi\Registry\CacheAdapter; use Psr\SimpleCache\CacheInterface; use Symfony\Component\Cache\Exception\InvalidArgumentException; @@ -77,11 +76,11 @@ public function getWithId(int $schemaId): ?AvroSchema public function getIdWithHash(string $hash): ?int { $rawId = $this->cache->get($hash); - + if (null === $rawId) { return null; } - + return (int) $rawId; } diff --git a/src/Registry/BlockingRegistry.php b/src/Registry/Decorators/BlockingDecorator.php similarity index 95% rename from src/Registry/BlockingRegistry.php rename to src/Registry/Decorators/BlockingDecorator.php index 27c480f..3c3f0a2 100644 --- a/src/Registry/BlockingRegistry.php +++ b/src/Registry/Decorators/BlockingDecorator.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace FlixTech\SchemaRegistryApi\Registry; +namespace FlixTech\SchemaRegistryApi\Registry\Decorators; use AvroSchema; use Exception; @@ -12,10 +12,7 @@ use GuzzleHttp\Promise\PromiseInterface; use LogicException; -/** - * {@inheritdoc} - */ -class BlockingRegistry implements SynchronousRegistry +class BlockingDecorator implements SynchronousRegistry { /** * @var AsynchronousRegistry diff --git a/src/Registry/CachedRegistry.php b/src/Registry/Decorators/CachingDecorator.php similarity index 97% rename from src/Registry/CachedRegistry.php rename to src/Registry/Decorators/CachingDecorator.php index 281e193..77d56de 100644 --- a/src/Registry/CachedRegistry.php +++ b/src/Registry/Decorators/CachingDecorator.php @@ -2,20 +2,18 @@ declare(strict_types=1); -namespace FlixTech\SchemaRegistryApi\Registry; +namespace FlixTech\SchemaRegistryApi\Registry\Decorators; use AvroSchema; use Exception; use FlixTech\SchemaRegistryApi\Exception\SchemaRegistryException; use FlixTech\SchemaRegistryApi\Registry; +use FlixTech\SchemaRegistryApi\Registry\Cache\CacheAdapter; use FlixTech\SchemaRegistryApi\Schema\AvroReference; use GuzzleHttp\Promise\PromiseInterface; use function call_user_func; -/** - * {@inheritdoc} - */ -class CachedRegistry implements Registry +class CachingDecorator implements Registry { /** * @var Registry diff --git a/src/Registry/PromisingRegistry.php b/src/Registry/GuzzlePromiseAsyncRegistry.php similarity index 56% rename from src/Registry/PromisingRegistry.php rename to src/Registry/GuzzlePromiseAsyncRegistry.php index 9c5c019..900b519 100644 --- a/src/Registry/PromisingRegistry.php +++ b/src/Registry/GuzzlePromiseAsyncRegistry.php @@ -7,28 +7,20 @@ use AvroSchema; use Closure; use FlixTech\SchemaRegistryApi\AsynchronousRegistry; +use FlixTech\SchemaRegistryApi\Constants; use FlixTech\SchemaRegistryApi\Exception\ExceptionMap; +use FlixTech\SchemaRegistryApi\Exception\RuntimeException; +use FlixTech\SchemaRegistryApi\Exception\SchemaRegistryException; +use FlixTech\SchemaRegistryApi\Json; +use FlixTech\SchemaRegistryApi\Requests; use FlixTech\SchemaRegistryApi\Schema\AvroReference; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Promise\PromiseInterface; -use InvalidArgumentException; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use RuntimeException; -use const FlixTech\SchemaRegistryApi\Constants\VERSION_LATEST; -use function FlixTech\SchemaRegistryApi\Requests\checkIfSubjectHasSchemaRegisteredRequest; -use function FlixTech\SchemaRegistryApi\Requests\registerNewSchemaVersionWithSubjectRequest; -use function FlixTech\SchemaRegistryApi\Requests\schemaRequest; -use function FlixTech\SchemaRegistryApi\Requests\singleSubjectVersionRequest; -use function FlixTech\SchemaRegistryApi\Requests\validateSchemaId; -use function FlixTech\SchemaRegistryApi\Requests\validateVersionId; -use function sprintf; - -/** - * {@inheritdoc} - */ -class PromisingRegistry implements AsynchronousRegistry + +class GuzzlePromiseAsyncRegistry implements AsynchronousRegistry { /** * @var ClientInterface @@ -45,22 +37,26 @@ public function __construct(ClientInterface $client) $this->client = $client; $exceptionMap = ExceptionMap::instance(); - $this->rejectedCallback = static function (RequestException $exception) use ($exceptionMap) { - return $exceptionMap($exception); + $this->rejectedCallback = static function (RequestException $exception) use ($exceptionMap): SchemaRegistryException { + if (!$exception->hasResponse()) { + throw new RuntimeException('RequestException has no response to inspect', RuntimeException::errorCode(), $exception); + } + + return $exceptionMap($exception->getResponse()); /** @phpstan-ignore-line */ }; } /** * {@inheritdoc} - * + *x * @throws RuntimeException */ public function register(string $subject, AvroSchema $schema, AvroReference ...$references): PromiseInterface { - $request = registerNewSchemaVersionWithSubjectRequest((string) $schema, $subject, ...$references); + $request = Requests::registerNewSchemaVersionWithSubjectRequest((string) $schema, $subject, ...$references); $onFulfilled = function (ResponseInterface $response) { - return $this->getJsonFromResponseBody($response)['id']; + return Json::decodeResponse($response)['id']; }; return $this->makeRequest($request, $onFulfilled); @@ -73,10 +69,10 @@ public function register(string $subject, AvroSchema $schema, AvroReference ...$ */ public function schemaId(string $subject, AvroSchema $schema): PromiseInterface { - $request = checkIfSubjectHasSchemaRegisteredRequest($subject, (string) $schema); + $request = Requests::checkIfSubjectHasSchemaRegisteredRequest($subject, (string)$schema); $onFulfilled = function (ResponseInterface $response) { - return $this->getJsonFromResponseBody($response)['id']; + return Json::decodeResponse($response)['id']; }; return $this->makeRequest($request, $onFulfilled); @@ -89,11 +85,11 @@ public function schemaId(string $subject, AvroSchema $schema): PromiseInterface */ public function schemaForId(int $schemaId): PromiseInterface { - $request = schemaRequest(validateSchemaId($schemaId)); + $request = Requests::schemaRequest(Requests::validateSchemaId($schemaId)); $onFulfilled = function (ResponseInterface $response) { return AvroSchema::parse( - $this->getJsonFromResponseBody($response)['schema'] + Json::decodeResponse($response)['schema'] ); }; @@ -107,11 +103,11 @@ public function schemaForId(int $schemaId): PromiseInterface */ public function schemaForSubjectAndVersion(string $subject, int $version): PromiseInterface { - $request = singleSubjectVersionRequest($subject, validateVersionId($version)); + $request = Requests::singleSubjectVersionRequest($subject, Requests::validateVersionId($version)); $onFulfilled = function (ResponseInterface $response) { return AvroSchema::parse( - $this->getJsonFromResponseBody($response)['schema'] + Json::decodeResponse($response)['schema'] ); }; @@ -125,10 +121,10 @@ public function schemaForSubjectAndVersion(string $subject, int $version): Promi */ public function schemaVersion(string $subject, AvroSchema $schema): PromiseInterface { - $request = checkIfSubjectHasSchemaRegisteredRequest($subject, (string) $schema); + $request = Requests::checkIfSubjectHasSchemaRegisteredRequest($subject, (string)$schema); $onFulfilled = function (ResponseInterface $response) { - return $this->getJsonFromResponseBody($response)['version']; + return Json::decodeResponse($response)['version']; }; return $this->makeRequest($request, $onFulfilled); @@ -141,11 +137,11 @@ public function schemaVersion(string $subject, AvroSchema $schema): PromiseInter */ public function latestVersion(string $subject): PromiseInterface { - $request = singleSubjectVersionRequest($subject, VERSION_LATEST); + $request = Requests::singleSubjectVersionRequest($subject, Constants::VERSION_LATEST); $onFulfilled = function (ResponseInterface $response) { return AvroSchema::parse( - $this->getJsonFromResponseBody($response)['schema'] + Json::decodeResponse($response)['schema'] ); }; @@ -164,31 +160,4 @@ private function makeRequest(RequestInterface $request, callable $onFulfilled): ->sendAsync($request) ->then($onFulfilled, $this->rejectedCallback); } - - /** - * @param ResponseInterface $response - * @return array - */ - private function getJsonFromResponseBody(ResponseInterface $response): array - { - $body = (string) $response->getBody(); - - try { - $decoded = \GuzzleHttp\json_decode($body, true); - - if (!is_array($decoded)) { - throw new InvalidArgumentException( - sprintf('response content "%s" is not a valid json object', $body) - ); - } - - return $decoded; - } catch (InvalidArgumentException $e) { - throw new InvalidArgumentException( - sprintf('%s - with content "%s"', $e->getMessage(), $body), - $e->getCode(), - $e - ); - } - } } diff --git a/src/Registry/Psr18SyncRegistry.php b/src/Registry/Psr18SyncRegistry.php new file mode 100644 index 0000000..5edd18d --- /dev/null +++ b/src/Registry/Psr18SyncRegistry.php @@ -0,0 +1,136 @@ +client = $client; + } + + public function register(string $subject, AvroSchema $schema, AvroReference ...$references): int + { + $request = Requests::registerNewSchemaVersionWithSubjectRequest((string)$schema, $subject, ...$references); + + $response = $this->makeRequest($request); + $this->guardAgainstErrorResponse($response); + + return Json::decodeResponse($response)['id']; + } + + public function schemaVersion(string $subject, AvroSchema $schema): int + { + $request = Requests::checkIfSubjectHasSchemaRegisteredRequest($subject, (string)$schema); + + $response = $this->makeRequest($request); + $this->guardAgainstErrorResponse($response); + + return Json::decodeResponse($response)['version']; + } + + public function latestVersion(string $subject): AvroSchema + { + $request = Requests::singleSubjectVersionRequest($subject, Constants::VERSION_LATEST); + + $response = $this->makeRequest($request); + $this->guardAgainstErrorResponse($response); + + return $this->parseAvroSchema(Json::decodeResponse($response)['schema']); + } + + public function schemaId(string $subject, AvroSchema $schema): int + { + $request = Requests::checkIfSubjectHasSchemaRegisteredRequest($subject, (string)$schema); + + $response = $this->makeRequest($request); + $this->guardAgainstErrorResponse($response); + + return Json::decodeResponse($response)['id']; + } + + public function schemaForId(int $schemaId): AvroSchema + { + $request = Requests::schemaRequest(Requests::validateSchemaId($schemaId)); + + $response = $this->makeRequest($request); + $this->guardAgainstErrorResponse($response); + + return $this->parseAvroSchema(Json::decodeResponse($response)['schema']); + } + + public function schemaForSubjectAndVersion(string $subject, int $version): AvroSchema + { + $request = Requests::singleSubjectVersionRequest($subject, Requests::validateVersionId($version)); + + $response = $this->makeRequest($request); + $this->guardAgainstErrorResponse($response); + + return $this->parseAvroSchema(Json::decodeResponse($response)['schema']); + } + + /** + * @param ResponseInterface $response + * + * @throws SchemaRegistryException + */ + private function guardAgainstErrorResponse(ResponseInterface $response): void + { + $map = ExceptionMap::instance(); + + if (!$map->isHttpError($response)) { + return; + } + + throw $map($response); + } + + private function makeRequest(RequestInterface $request): ResponseInterface + { + try { + return $this->client->sendRequest($request); + } catch (ClientExceptionInterface $exception) { + throw new RuntimeException( + "Unexpected error during client request", + RuntimeException::ERROR_CODE, + $exception + ); + } + } + + private function parseAvroSchema(string $schema): AvroSchema + { + try { + return AvroSchema::parse($schema); + } catch (AvroSchemaParseException $e) { + throw new InvalidAvroSchemaException( + "Could not parse schema: $schema", + InvalidAvroSchemaException::ERROR_CODE, + $e + ); + } + } +} diff --git a/src/Requests.php b/src/Requests.php new file mode 100644 index 0000000..a1a5774 --- /dev/null +++ b/src/Requests.php @@ -0,0 +1,224 @@ + $schema + ]; + + return count($references) === 0 + ? Json::encode($return) + : Json::encode(array_merge($return, ['references' => $references])); + } + + public static function registerNewSchemaVersionWithSubjectRequest(string $schema, string $subjectName, AvroReference ...$references): RequestInterface + { + return new Request( + 'POST', + Utils::uriFor("subjects/$subjectName/versions"), + Constants::CONTENT_TYPE_HEADER + Constants::ACCEPT_HEADER, + self::prepareJsonSchemaForTransfer(Json::validateStringAsJson($schema), ...$references), + ); + } + + public static function checkSchemaCompatibilityAgainstVersionRequest(string $schema, string $subjectName, string $versionId): RequestInterface + { + return new Request( + 'POST', + Utils::uriFor("compatibility/subjects/$subjectName/versions/$versionId"), + Constants::CONTENT_TYPE_HEADER + Constants::ACCEPT_HEADER, + self::prepareJsonSchemaForTransfer(Json::validateStringAsJson($schema)) + ); + } + + public static function checkIfSubjectHasSchemaRegisteredRequest(string $subjectName, string $schema): RequestInterface + { + return new Request( + 'POST', + Utils::uriFor("subjects/$subjectName"), + Constants::CONTENT_TYPE_HEADER + Constants::ACCEPT_HEADER, + self::prepareJsonSchemaForTransfer(Json::validateStringAsJson($schema)) + ); + } + + public static function schemaRequest(string $id): RequestInterface + { + return new Request( + 'GET', + Utils::uriFor("schemas/ids/$id"), + Constants::ACCEPT_HEADER + ); + } + + public static function defaultCompatibilityLevelRequest(): RequestInterface + { + return new Request( + 'GET', + 'config', + Constants::ACCEPT_HEADER + ); + } + + public static function validateCompatibilityLevel(string $compatibilityVersion): string + { + $compatibilities = [ + Constants::COMPATIBILITY_NONE, + Constants::COMPATIBILITY_BACKWARD, + Constants::COMPATIBILITY_BACKWARD_TRANSITIVE, + Constants::COMPATIBILITY_FORWARD, + Constants::COMPATIBILITY_FORWARD_TRANSITIVE, + Constants::COMPATIBILITY_FULL, + Constants::COMPATIBILITY_FULL_TRANSITIVE, + + ]; + Assert::that($compatibilityVersion)->inArray( + $compatibilities, + '$level must be one of ' . implode(', ', $compatibilities) + ); + + return $compatibilityVersion; + } + + public static function prepareCompatibilityLevelForTransport(string $compatibilityLevel): string + { + return Json::encode(['compatibility' => $compatibilityLevel]); + } + + public static function changeDefaultCompatibilityLevelRequest(string $level): RequestInterface + { + return new Request( + 'PUT', + 'config', + Constants::ACCEPT_HEADER, + self::prepareCompatibilityLevelForTransport(self::validateCompatibilityLevel($level)) + ); + } + + public static function subjectCompatibilityLevelRequest(string $subjectName): RequestInterface + { + return new Request( + 'GET', + Utils::uriFor("config/$subjectName"), + Constants::ACCEPT_HEADER + ); + } + + public static function changeSubjectCompatibilityLevelRequest(string $subjectName, string $level): RequestInterface + { + return new Request( + 'PUT', + Utils::uriFor("config/$subjectName"), + Constants::ACCEPT_HEADER, + self::prepareCompatibilityLevelForTransport(self::validateCompatibilityLevel($level)) + ); + } + + /** + * @param int|string $versionId + * @return string + */ + public static function validateVersionId($versionId): string + { + if (Constants::VERSION_LATEST !== $versionId) { + Assert::that($versionId) + ->integerish('$versionId must be an integer of type int or string') + ->between(1, 2 ** 31 - 1, '$versionId must be between 1 and 2^31 - 1'); + } + + return (string)$versionId; + } + + /** + * @param int|string $schemaId + * @return string + */ + public static function validateSchemaId($schemaId): string + { + Assert::that($schemaId) + ->integerish('$schemaId must be an integer value of type int or string') + ->greaterThan(0, '$schemaId must be greater than 0'); + + return (string)$schemaId; + } + + /** + * @param string $subjectName + * @param bool $permanent + * @return RequestInterface + */ + public static function deleteSubjectRequest(string $subjectName, bool $permanent = false): RequestInterface + { + $query = $permanent ? "true" : "false"; + + return new Request( + 'DELETE', + Utils::uriFor("subjects/$subjectName?permanent=$query"), + Constants::ACCEPT_HEADER + ); + } + + /** + * @param string $subjectName + * @param string $versionId + * @param bool $permanent + * @return RequestInterface + */ + public static function deleteSubjectVersionRequest(string $subjectName, string $versionId, bool $permanent = false): RequestInterface + { + $query = $permanent ? "true" : "false"; + + return new Request( + 'DELETE', + Utils::uriFor("subjects/$subjectName/versions/$versionId?permanent=$query"), + Constants::ACCEPT_HEADER + ); + } + + private function __clone() + { + } +} diff --git a/src/Requests/Functions.php b/src/Requests/Functions.php index 9462a92..078e718 100644 --- a/src/Requests/Functions.php +++ b/src/Requests/Functions.php @@ -3,9 +3,11 @@ namespace FlixTech\SchemaRegistryApi\Requests; use Assert\Assert; -use FlixTech\SchemaRegistryApi\Schema\AvroReference; use GuzzleHttp\Psr7\Request; +use InvalidArgumentException; +use JsonException; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; use const FlixTech\SchemaRegistryApi\Constants\ACCEPT_HEADER; use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_BACKWARD; use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_BACKWARD_TRANSITIVE; @@ -17,7 +19,65 @@ use const FlixTech\SchemaRegistryApi\Constants\CONTENT_TYPE_HEADER; use const FlixTech\SchemaRegistryApi\Constants\VERSION_LATEST; use function implode; +use function json_decode; +use function sprintf; +/** + * @param string $jsonString + * @param int<1, max> $depth + * + * @return mixed + * + * @throws JsonException + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Json::jsonDecode instead + */ +function jsonDecode(string $jsonString, int $depth = 512) +{ + return json_decode($jsonString, true, $depth, JSON_THROW_ON_ERROR); +} + +/** + * @param mixed $data + * + * @return string + * + * @throws JsonException + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Json::jsonEncode instead + */ +function jsonEncode($data): string +{ + return json_encode($data, JSON_THROW_ON_ERROR); +} + +/** + * @param ResponseInterface $response + * + * @return array + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Json::decodeResponse instead + */ +function decodeResponse(ResponseInterface $response): array +{ + $body = (string)$response->getBody(); + + try { + return jsonDecode($body); + } catch (JsonException $e) { + throw new InvalidArgumentException( + sprintf('%s - with content "%s"', $e->getMessage(), $body), + $e->getCode(), + $e + ); + } +} + +/** + * @return RequestInterface + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::allSubjectsRequest instead + */ function allSubjectsRequest(): RequestInterface { return new Request( @@ -27,71 +87,117 @@ function allSubjectsRequest(): RequestInterface ); } +/** + * @param string $subjectName + * + * @return RequestInterface + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::allSubjectVersionsRequest instead + */ function allSubjectVersionsRequest(string $subjectName): RequestInterface { return new Request( 'GET', - sprintf('subjects/%s/versions', $subjectName), + sprintf("subjects/%s/versions", $subjectName), ACCEPT_HEADER ); } +/** + * @param string $subjectName + * @param string $versionId + * + * @return RequestInterface + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::singleSubjectVersionRequest instead + */ function singleSubjectVersionRequest(string $subjectName, string $versionId): RequestInterface { return new Request( 'GET', - sprintf( - 'subjects/%s/versions/%s', - $subjectName, - $versionId, - ), + sprintf("subjects/%s/versions/%s", $subjectName, $versionId), ACCEPT_HEADER ); } -function registerNewSchemaVersionWithSubjectRequest(string $schema, string $subjectName, AvroReference ...$references): RequestInterface +/** + * @param string $schema + * @param string $subjectName + * + * @return RequestInterface + * + * @throws JsonException + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::registerNewSchemaVersionWithSubjectRequest instead + */ +function registerNewSchemaVersionWithSubjectRequest(string $schema, string $subjectName): RequestInterface { return new Request( 'POST', - sprintf('subjects/%s/versions', $subjectName), + sprintf("subjects/%s/versions", $subjectName), CONTENT_TYPE_HEADER + ACCEPT_HEADER, - prepareJsonSchemaForTransfer(validateSchemaStringAsJson($schema), ...$references) + prepareJsonSchemaForTransfer(validateSchemaStringAsJson($schema)) ); } +/** + * @param string $schema + * @param string $subjectName + * @param string $versionId + * + * @return RequestInterface + * + * @throws JsonException + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::checkSchemaCompatibilityAgainstVersionRequest instead + */ function checkSchemaCompatibilityAgainstVersionRequest(string $schema, string $subjectName, string $versionId): RequestInterface { return new Request( 'POST', - sprintf( - 'compatibility/subjects/%s/versions/%s', - $subjectName, - $versionId, - ), + sprintf("compatibility/subjects/%s/versions/%s", $subjectName, $versionId), CONTENT_TYPE_HEADER + ACCEPT_HEADER, prepareJsonSchemaForTransfer(validateSchemaStringAsJson($schema)) ); } +/** + * @param string $subjectName + * @param string $schema + * + * @return RequestInterface + * + * @throws JsonException + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::checkIfSubjectHasSchemaRegisteredRequest instead + */ function checkIfSubjectHasSchemaRegisteredRequest(string $subjectName, string $schema): RequestInterface { return new Request( 'POST', - sprintf('subjects/%s', $subjectName), + sprintf("subjects/%s", $subjectName), CONTENT_TYPE_HEADER + ACCEPT_HEADER, prepareJsonSchemaForTransfer(validateSchemaStringAsJson($schema)) ); } +/** + * @param string $id + * @return RequestInterface + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::schemaRequest instead + */ function schemaRequest(string $id): RequestInterface { return new Request( 'GET', - sprintf('schemas/ids/%s', $id), + sprintf("schemas/ids/%s", $id), ACCEPT_HEADER ); } +/** + * @return RequestInterface + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::defaultCompatibilityLevelRequest instead + */ function defaultCompatibilityLevelRequest(): RequestInterface { return new Request( @@ -101,6 +207,14 @@ function defaultCompatibilityLevelRequest(): RequestInterface ); } +/** + * @param string $level + * + * @return RequestInterface + * + * @throws JsonException + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::changeDefaultCompatibilityLevelRequest instead + */ function changeDefaultCompatibilityLevelRequest(string $level): RequestInterface { return new Request( @@ -111,20 +225,36 @@ function changeDefaultCompatibilityLevelRequest(string $level): RequestInterface ); } +/** + * @param string $subjectName + * + * @return RequestInterface + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::subjectCompatibilityLevelRequest instead + */ function subjectCompatibilityLevelRequest(string $subjectName): RequestInterface { return new Request( 'GET', - sprintf('config/%s', $subjectName), + sprintf("config/%s", $subjectName), ACCEPT_HEADER ); } +/** + * @param string $subjectName + * @param string $level + * + * @return RequestInterface + * + * @throws JsonException + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::changeSubjectCompatibilityLevelRequest instead + */ function changeSubjectCompatibilityLevelRequest(string $subjectName, string $level): RequestInterface { return new Request( 'PUT', - sprintf('config/%s', $subjectName), + sprintf("config/%s", $subjectName), ACCEPT_HEADER, prepareCompatibilityLevelForTransport(validateCompatibilityLevel($level)) ); @@ -132,7 +262,10 @@ function changeSubjectCompatibilityLevelRequest(string $subjectName, string $lev /** * @param int|string $versionId + * * @return string + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::validateVersionId instead */ function validateVersionId($versionId): string { @@ -142,9 +275,16 @@ function validateVersionId($versionId): string ->between(1, 2 ** 31 - 1, '$versionId must be between 1 and 2^31 - 1'); } - return (string) $versionId; + return (string)$versionId; } +/** + * @param string $schema + * + * @return string + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::validateSchemaStringAsJson instead + */ function validateSchemaStringAsJson(string $schema): string { Assert::that($schema)->isJsonString('$schema must be a valid JSON string'); @@ -152,17 +292,32 @@ function validateSchemaStringAsJson(string $schema): string return $schema; } -function prepareJsonSchemaForTransfer(string $schema, AvroReference ...$references): string +/** + * @param string $schema + * + * @return string + * + * @throws JsonException + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::prepareJsonSchemaForTransfer instead + */ +function prepareJsonSchemaForTransfer(string $schema): string { - $return = [ - 'schema' => $schema - ]; + $decoded = jsonDecode($schema); + + if (is_array($decoded) && array_key_exists('schema', $decoded)) { + return jsonEncode($decoded); + } - return !$references - ? \GuzzleHttp\json_encode($return) - : \GuzzleHttp\json_encode(array_merge($return, ['references' => $references])); + return jsonEncode(['schema' => jsonEncode($decoded)]); } +/** + * @param string $compatibilityVersion + * + * @return string + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::validateCompatibilityLevel instead + */ function validateCompatibilityLevel(string $compatibilityVersion): string { $compatibilities = [ @@ -183,14 +338,24 @@ function validateCompatibilityLevel(string $compatibilityVersion): string return $compatibilityVersion; } +/** + * @param string $compatibilityLevel + * + * @return string + * + * @throws JsonException + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::prepareCompatibilityLevelForTransport instead + */ function prepareCompatibilityLevelForTransport(string $compatibilityLevel): string { - return \GuzzleHttp\json_encode(['compatibility' => $compatibilityLevel]); + return jsonEncode(['compatibility' => $compatibilityLevel]); } /** * @param int|string $schemaId * @return string + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::validateSchemaId instead */ function validateSchemaId($schemaId): string { @@ -198,18 +363,21 @@ function validateSchemaId($schemaId): string ->integerish('$schemaId must be an integer value of type int or string') ->greaterThan(0, '$schemaId must be greater than 0'); - return (string) $schemaId; + return (string)$schemaId; } /** * @param string $subjectName + * * @return RequestInterface + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::deleteSubjectRequest instead */ function deleteSubjectRequest(string $subjectName): RequestInterface { return new Request( 'DELETE', - sprintf('subjects/%s', $subjectName), + sprintf("subjects/%s", $subjectName), ACCEPT_HEADER ); } @@ -217,13 +385,16 @@ function deleteSubjectRequest(string $subjectName): RequestInterface /** * @param string $subjectName * @param string $versionId + * * @return RequestInterface + * + * @deprecated Use \FlixTech\SchemaRegistryApi\Requests::deleteSubjectVersionRequest instead */ function deleteSubjectVersionRequest(string $subjectName, string $versionId): RequestInterface { return new Request( 'DELETE', - sprintf('subjects/%s/versions/%s', $subjectName, $versionId), + sprintf("subjects/%s/versions/%s", $subjectName, $versionId), ACCEPT_HEADER ); } diff --git a/src/Schemas/AvroSchemaType.php b/src/Schemas/AvroSchemaType.php new file mode 100644 index 0000000..785ccc1 --- /dev/null +++ b/src/Schemas/AvroSchemaType.php @@ -0,0 +1,39 @@ + + */ +final class AvroSchemaType extends SchemaTypes implements SchemaType +{ + /** + * @var AvroSchemaType + */ + private static $instance; + + public static function instance(): AvroSchemaType + { + if (self::$instance !== null) { + return self::$instance; + } + + self::$instance = new self(); + + return self::$instance; + } + + public function value(): string + { + return Constants::AVRO_TYPE; + } + + public function __toString(): string + { + return $this->value(); + } +} diff --git a/src/Schemas/JsonSchemaType.php b/src/Schemas/JsonSchemaType.php new file mode 100644 index 0000000..ae6bc07 --- /dev/null +++ b/src/Schemas/JsonSchemaType.php @@ -0,0 +1,39 @@ + + */ +final class JsonSchemaType extends SchemaTypes implements SchemaType +{ + /** + * @var JsonSchemaType + */ + private static $instance; + + public static function instance(): JsonSchemaType + { + if (self::$instance !== null) { + return self::$instance; + } + + self::$instance = new self(); + + return self::$instance; + } + + public function value(): string + { + return Constants::JSON_TYPE; + } + + public function __toString(): string + { + return $this->value(); + } +} diff --git a/src/Schemas/ProtobufSchemaType.php b/src/Schemas/ProtobufSchemaType.php new file mode 100644 index 0000000..d7420fd --- /dev/null +++ b/src/Schemas/ProtobufSchemaType.php @@ -0,0 +1,39 @@ + + */ +final class ProtobufSchemaType extends SchemaTypes implements SchemaType +{ + /** + * @var ProtobufSchemaType + */ + private static $instance; + + public static function instance(): ProtobufSchemaType + { + if (self::$instance !== null) { + return self::$instance; + } + + self::$instance = new self(); + + return self::$instance; + } + + public function value(): string + { + return Constants::PROTOBUF_TYPE; + } + + public function __toString(): string + { + return $this->value(); + } +} diff --git a/src/Schemas/SchemaType.php b/src/Schemas/SchemaType.php new file mode 100644 index 0000000..5fe834c --- /dev/null +++ b/src/Schemas/SchemaType.php @@ -0,0 +1,18 @@ + 'application/vnd.schemaregistry.v1+json'], - '{"error_code":42201,"message": "Invalid Avro schema"}' - ) + new Response( + 422, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":42201,"message": "Invalid Avro schema"}' ) ) ); @@ -59,14 +53,10 @@ public function it_should_handle_IncompatibleAvroSchema_code(): void 'Incompatible Avro schema', 409, (ExceptionMap::instance())( - new RequestException( - '409 Conflict', - new Request('GET', '/'), - new Response( - 409, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"error_code":409,"message": "Incompatible Avro schema"}' - ) + new Response( + 409, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":409,"message": "Incompatible Avro schema"}' ) ) ); @@ -82,14 +72,10 @@ public function it_should_handle_BackendDataStore_code(): void 'Error in the backend datastore', 50001, (ExceptionMap::instance())( - new RequestException( - '500 Internal Server Error', - new Request('GET', '/'), - new Response( - 500, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"error_code":50001,"message": "Error in the backend datastore"}' - ) + new Response( + 500, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":50001,"message": "Error in the backend datastore"}' ) ) ); @@ -105,14 +91,10 @@ public function it_should_handle_InvalidCompatibilityLevel_code(): void 'Invalid compatibility level', 42203, (ExceptionMap::instance())( - new RequestException( - '422 Unprocessable Entity', - new Request('GET', '/'), - new Response( - 422, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"error_code":42203,"message": "Invalid compatibility level"}' - ) + new Response( + 422, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":42203,"message": "Invalid compatibility level"}' ) ) ); @@ -128,14 +110,10 @@ public function it_should_handle_InvalidVersion_code(): void 'Invalid version', 42202, (ExceptionMap::instance())( - new RequestException( - '422 Unprocessable Entity', - new Request('GET', '/'), - new Response( - 422, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"error_code":42202,"message": "Invalid version"}' - ) + new Response( + 422, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":42202,"message": "Invalid version"}' ) ) ); @@ -151,14 +129,10 @@ public function it_should_handle_MasterProxy_code(): void 'Error while forwarding the request to the master', 50003, (ExceptionMap::instance())( - new RequestException( - '500 Internal server Error', - new Request('GET', '/'), - new Response( - 500, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"error_code":50003,"message": "Error while forwarding the request to the master"}' - ) + new Response( + 500, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":50003,"message": "Error while forwarding the request to the master"}' ) ) ); @@ -174,14 +148,10 @@ public function it_should_handle_OperationTimedOut_code(): void 'Operation timed out', 50002, (ExceptionMap::instance())( - new RequestException( - '500 Internal server Error', - new Request('GET', '/'), - new Response( - 500, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"error_code":50002,"message": "Operation timed out"}' - ) + new Response( + 500, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":50002,"message": "Operation timed out"}' ) ) ); @@ -197,14 +167,10 @@ public function it_should_handle_SchemaNotFound_code(): void 'Schema not found', 40403, (ExceptionMap::instance())( - new RequestException( - '404 Not Found', - new Request('GET', '/'), - new Response( - 404, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"error_code":40403,"message": "Schema not found"}' - ) + new Response( + 404, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":40403,"message": "Schema not found"}' ) ) ); @@ -220,14 +186,10 @@ public function it_should_handle_SubjectNotFound_code(): void 'Subject not found', 40401, (ExceptionMap::instance())( - new RequestException( - '404 Not Found', - new Request('GET', '/'), - new Response( - 404, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"error_code":40401,"message": "Subject not found"}' - ) + new Response( + 404, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":40401,"message": "Subject not found"}' ) ) ); @@ -243,35 +205,15 @@ public function it_should_handle_VersionNotFound_code(): void 'Version not found', 40402, (ExceptionMap::instance())( - new RequestException( - '404 Not Found', - new Request('GET', '/'), - new Response( - 404, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"error_code":40402,"message": "Version not found"}' - ) + new Response( + 404, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":40402,"message": "Version not found"}' ) ) ); } - /** - * @test - */ - public function it_should_not_process_exceptions_with_missing_response(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage("RequestException has no response to inspect"); - - (ExceptionMap::instance())( - new RequestException( - '404 Not Found', - new Request('GET', '/') - ) - ); - } - /** * @test */ @@ -290,14 +232,10 @@ public function it_should_not_process_exceptions_with_missing_error_codes(): voi $this->expectExceptionMessage('Invalid message body received - cannot find "error_code" field in response body'); (ExceptionMap::instance())( - new RequestException( - '404 Not Found', - new Request('GET', '/'), - new Response( - 404, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"message": "This JSON has no \'error_code\' field."}' - ) + new Response( + 404, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"message": "This JSON has no \'error_code\' field."}' ) ); } @@ -311,14 +249,10 @@ public function it_should_not_process_unknown_error_codes(): void $this->expectExceptionMessage('Unknown error code "99999"'); (ExceptionMap::instance())( - new RequestException( - '404 Not Found', - new Request('GET', '/'), - new Response( - 404, - ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], - '{"error_code":99999,"message": "Subject not found"}' - ) + new Response( + 404, + ['Content-Type' => 'application/vnd.schemaregistry.v1+json'], + '{"error_code":99999,"message": "Subject not found"}' ) ); } @@ -328,8 +262,7 @@ private function assertSchemaRegistryException( string $expectedMessage, int $errorCode, SchemaRegistryException $exception - ): void - { + ): void { self::assertInstanceOf($exceptionClass, $exception); self::assertEquals($errorCode, $exception->getCode()); self::assertEquals($expectedMessage, $exception->getMessage()); diff --git a/test/IntegrationTest.php b/test/IntegrationTest.php index 421ea06..0b59c01 100644 --- a/test/IntegrationTest.php +++ b/test/IntegrationTest.php @@ -4,6 +4,7 @@ namespace FlixTech\SchemaRegistryApi\Test; +use FlixTech\SchemaRegistryApi\Constants; use FlixTech\SchemaRegistryApi\Exception\ExceptionMap; use FlixTech\SchemaRegistryApi\Exception\IncompatibleAvroSchemaException; use FlixTech\SchemaRegistryApi\Exception\InvalidAvroSchemaException; @@ -11,26 +12,13 @@ use FlixTech\SchemaRegistryApi\Exception\SchemaNotFoundException; use FlixTech\SchemaRegistryApi\Exception\SubjectNotFoundException; use FlixTech\SchemaRegistryApi\Exception\VersionNotFoundException; +use FlixTech\SchemaRegistryApi\Json; +use FlixTech\SchemaRegistryApi\Requests; use GuzzleHttp\Client; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\RequestException; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; -use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_BACKWARD; -use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_FORWARD; -use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_FULL; -use const FlixTech\SchemaRegistryApi\Constants\VERSION_LATEST; -use function FlixTech\SchemaRegistryApi\Requests\allSubjectsRequest; -use function FlixTech\SchemaRegistryApi\Requests\allSubjectVersionsRequest; -use function FlixTech\SchemaRegistryApi\Requests\changeDefaultCompatibilityLevelRequest; -use function FlixTech\SchemaRegistryApi\Requests\changeSubjectCompatibilityLevelRequest; -use function FlixTech\SchemaRegistryApi\Requests\checkIfSubjectHasSchemaRegisteredRequest; -use function FlixTech\SchemaRegistryApi\Requests\checkSchemaCompatibilityAgainstVersionRequest; -use function FlixTech\SchemaRegistryApi\Requests\defaultCompatibilityLevelRequest; -use function FlixTech\SchemaRegistryApi\Requests\registerNewSchemaVersionWithSubjectRequest; -use function FlixTech\SchemaRegistryApi\Requests\schemaRequest; -use function FlixTech\SchemaRegistryApi\Requests\singleSubjectVersionRequest; -use function FlixTech\SchemaRegistryApi\Requests\subjectCompatibilityLevelRequest; /** * @group integration @@ -118,36 +106,36 @@ protected function setUp(): void public function managing_subjects_and_versions(): void { $this->client - ->sendAsync(allSubjectsRequest()) + ->sendAsync(Requests::allSubjectsRequest()) ->then( function (ResponseInterface $request) { - $this->assertEmpty(\GuzzleHttp\json_decode($request->getBody()->getContents(), true)); + $this->assertEmpty(Json::decode($request->getBody()->getContents())); } )->wait(); $this->client - ->sendAsync(registerNewSchemaVersionWithSubjectRequest($this->baseSchema, self::SUBJECT_NAME)) + ->sendAsync(Requests::registerNewSchemaVersionWithSubjectRequest($this->baseSchema, self::SUBJECT_NAME)) ->then( function (ResponseInterface $request) { - $this->assertEquals(1, \GuzzleHttp\json_decode($request->getBody()->getContents(), true)['id']); + $this->assertEquals(1, Json::decode($request->getBody()->getContents())['id']); } )->wait(); $this->client - ->sendAsync(schemaRequest('1')) + ->sendAsync(Requests::schemaRequest('1')) ->then( function (ResponseInterface $request) { - $decodedBody = \GuzzleHttp\json_decode($request->getBody()->getContents(), true); + $decodedBody = Json::decode($request->getBody()->getContents()); $this->assertJsonStringEqualsJsonString($this->baseSchema, $decodedBody['schema']); } )->wait(); $this->client - ->sendAsync(checkIfSubjectHasSchemaRegisteredRequest(self::SUBJECT_NAME, $this->baseSchema)) + ->sendAsync(Requests::checkIfSubjectHasSchemaRegisteredRequest(self::SUBJECT_NAME, $this->baseSchema)) ->then( function (ResponseInterface $request) { - $decodedBody = \GuzzleHttp\json_decode($request->getBody()->getContents(), true); + $decodedBody = Json::decode($request->getBody()->getContents()); $this->assertEquals(1, $decodedBody['id']); $this->assertEquals(1, $decodedBody['version']); @@ -157,10 +145,10 @@ function (ResponseInterface $request) { )->wait(); $this->client - ->sendAsync(singleSubjectVersionRequest(self::SUBJECT_NAME, VERSION_LATEST)) + ->sendAsync(Requests::singleSubjectVersionRequest(self::SUBJECT_NAME, Constants::VERSION_LATEST)) ->then( function (ResponseInterface $request) { - $decodedBody = \GuzzleHttp\json_decode($request->getBody()->getContents(), true); + $decodedBody = Json::decode($request->getBody()->getContents()); $this->assertEquals(self::SUBJECT_NAME, $decodedBody['subject']); $this->assertEquals(1, $decodedBody['version']); @@ -170,100 +158,100 @@ function (ResponseInterface $request) { )->wait(); $this->client - ->sendAsync(checkSchemaCompatibilityAgainstVersionRequest( + ->sendAsync(Requests::checkSchemaCompatibilityAgainstVersionRequest( $this->compatibleSchemaEvolution, self::SUBJECT_NAME, - VERSION_LATEST + Constants::VERSION_LATEST ))->then( function (ResponseInterface $request) { - $decodedBody = \GuzzleHttp\json_decode($request->getBody()->getContents(), true); + $decodedBody = Json::decode($request->getBody()->getContents()); $this->assertTrue($decodedBody['is_compatible']); } )->wait(); $this->client - ->sendAsync(checkSchemaCompatibilityAgainstVersionRequest( + ->sendAsync(Requests::checkSchemaCompatibilityAgainstVersionRequest( $this->incompatibleSchemaEvolution, self::SUBJECT_NAME, - VERSION_LATEST + Constants::VERSION_LATEST ))->otherwise( function (RequestException $exception) { $this->assertInstanceOf( IncompatibleAvroSchemaException::class, - (ExceptionMap::instance())($exception) + (ExceptionMap::instance())($exception->getResponse()) /** @phpstan-ignore-line */ ); } )->wait(); $this->client - ->sendAsync(registerNewSchemaVersionWithSubjectRequest($this->invalidSchema, self::SUBJECT_NAME)) + ->sendAsync(Requests::registerNewSchemaVersionWithSubjectRequest($this->invalidSchema, self::SUBJECT_NAME)) ->otherwise( function (RequestException $exception) { $this->assertInstanceOf( InvalidAvroSchemaException::class, - (ExceptionMap::instance())($exception) + (ExceptionMap::instance())($exception->getResponse()) /** @phpstan-ignore-line */ ); } )->wait(); $this->client - ->sendAsync(singleSubjectVersionRequest('INVALID', VERSION_LATEST)) + ->sendAsync(Requests::singleSubjectVersionRequest('INVALID', Constants::VERSION_LATEST)) ->otherwise( function (RequestException $exception) { $this->assertInstanceOf( SubjectNotFoundException::class, - (ExceptionMap::instance())($exception) + (ExceptionMap::instance())($exception->getResponse()) /** @phpstan-ignore-line */ ); } )->wait(); $this->client - ->sendAsync(singleSubjectVersionRequest(self::SUBJECT_NAME, 'INVALID')) + ->sendAsync(Requests::singleSubjectVersionRequest(self::SUBJECT_NAME, 'INVALID')) ->otherwise( function (RequestException $exception) { $this->assertInstanceOf( InvalidVersionException::class, - (ExceptionMap::instance())($exception) + (ExceptionMap::instance())($exception->getResponse()) /** @phpstan-ignore-line */ ); } )->wait(); $this->client - ->sendAsync(singleSubjectVersionRequest(self::SUBJECT_NAME, '5')) + ->sendAsync(Requests::singleSubjectVersionRequest(self::SUBJECT_NAME, '5')) ->otherwise( function (RequestException $exception) { $this->assertInstanceOf( VersionNotFoundException::class, - (ExceptionMap::instance())($exception) + (ExceptionMap::instance())($exception->getResponse()) /** @phpstan-ignore-line */ ); } )->wait(); $this->client - ->sendAsync(schemaRequest('6')) + ->sendAsync(Requests::schemaRequest('6')) ->otherwise( function (RequestException $exception) { $this->assertInstanceOf( SchemaNotFoundException::class, - (ExceptionMap::instance())($exception) + (ExceptionMap::instance())($exception->getResponse()) /** @phpstan-ignore-line */ ); } )->wait(); $this->client - ->sendAsync(registerNewSchemaVersionWithSubjectRequest($this->compatibleSchemaEvolution, self::SUBJECT_NAME)) + ->sendAsync(Requests::registerNewSchemaVersionWithSubjectRequest($this->compatibleSchemaEvolution, self::SUBJECT_NAME)) ->then( function (ResponseInterface $request) { - $this->assertEquals(2, \GuzzleHttp\json_decode($request->getBody()->getContents(), true)['id']); + $this->assertEquals(2, Json::decode($request->getBody()->getContents())['id']); } )->wait(); $this->client - ->sendAsync(allSubjectVersionsRequest(self::SUBJECT_NAME)) + ->sendAsync(Requests::allSubjectVersionsRequest(self::SUBJECT_NAME)) ->then( function (ResponseInterface $request) { - $this->assertEquals([1, 2], \GuzzleHttp\json_decode($request->getBody()->getContents(), true)); + $this->assertEquals([1, 2], Json::decode($request->getBody()->getContents())); } )->wait(); } @@ -274,52 +262,52 @@ function (ResponseInterface $request) { public function managing_compatibility_levels(): void { $this->client - ->sendAsync(defaultCompatibilityLevelRequest()) + ->sendAsync(Requests::defaultCompatibilityLevelRequest()) ->then( function (ResponseInterface $request) { - $decodedBody = \GuzzleHttp\json_decode($request->getBody()->getContents(), true); + $decodedBody = Json::decode($request->getBody()->getContents()); $this->assertEquals( - COMPATIBILITY_BACKWARD, + Constants::COMPATIBILITY_BACKWARD, $decodedBody['compatibilityLevel'] ); } )->wait(); $this->client - ->sendAsync(changeDefaultCompatibilityLevelRequest(COMPATIBILITY_FULL)) + ->sendAsync(Requests::changeDefaultCompatibilityLevelRequest(Constants::COMPATIBILITY_FULL)) ->then( function (ResponseInterface $request) { - $decodedBody = \GuzzleHttp\json_decode($request->getBody()->getContents(), true); + $decodedBody = Json::decode($request->getBody()->getContents()); $this->assertEquals( - COMPATIBILITY_FULL, + Constants::COMPATIBILITY_FULL, $decodedBody['compatibility'] ); } )->wait(); $this->client - ->sendAsync(changeSubjectCompatibilityLevelRequest(self::SUBJECT_NAME, COMPATIBILITY_FORWARD)) + ->sendAsync(Requests::changeSubjectCompatibilityLevelRequest(self::SUBJECT_NAME, Constants::COMPATIBILITY_FORWARD)) ->then( function (ResponseInterface $request) { - $decodedBody = \GuzzleHttp\json_decode($request->getBody()->getContents(), true); + $decodedBody = Json::decode($request->getBody()->getContents()); $this->assertEquals( - COMPATIBILITY_FORWARD, + Constants::COMPATIBILITY_FORWARD, $decodedBody['compatibility'] ); } )->wait(); $this->client - ->sendAsync(subjectCompatibilityLevelRequest(self::SUBJECT_NAME)) + ->sendAsync(Requests::subjectCompatibilityLevelRequest(self::SUBJECT_NAME)) ->then( function (ResponseInterface $request) { - $decodedBody = \GuzzleHttp\json_decode($request->getBody()->getContents(), true); + $decodedBody = Json::decode($request->getBody()->getContents()); $this->assertEquals( - COMPATIBILITY_FORWARD, + Constants::COMPATIBILITY_FORWARD, $decodedBody['compatibilityLevel'] ); } diff --git a/test/Registry/Cache/AbstractCacheAdapterTestCase.php b/test/Registry/Cache/AbstractCacheAdapterTestCase.php index 228859f..500e19a 100644 --- a/test/Registry/Cache/AbstractCacheAdapterTestCase.php +++ b/test/Registry/Cache/AbstractCacheAdapterTestCase.php @@ -6,7 +6,7 @@ use AvroSchema; use AvroSchemaParseException; -use FlixTech\SchemaRegistryApi\Registry\CacheAdapter; +use FlixTech\SchemaRegistryApi\Registry\Cache\CacheAdapter; use PHPUnit\Framework\TestCase; abstract class AbstractCacheAdapterTestCase extends TestCase diff --git a/test/Registry/Cache/AvroObjectCacheAdapterTest.php b/test/Registry/Cache/AvroObjectCacheAdapterTest.php index 91e6dc3..6850c2a 100644 --- a/test/Registry/Cache/AvroObjectCacheAdapterTest.php +++ b/test/Registry/Cache/AvroObjectCacheAdapterTest.php @@ -5,7 +5,7 @@ namespace FlixTech\SchemaRegistryApi\Test\Registry\Cache; use FlixTech\SchemaRegistryApi\Registry\Cache\AvroObjectCacheAdapter; -use FlixTech\SchemaRegistryApi\Registry\CacheAdapter; +use FlixTech\SchemaRegistryApi\Registry\Cache\CacheAdapter; class AvroObjectCacheAdapterTest extends AbstractCacheAdapterTestCase { diff --git a/test/Registry/Cache/CacheItemPoolAdapterTest.php b/test/Registry/Cache/CacheItemPoolAdapterTest.php index 642c198..5972948 100644 --- a/test/Registry/Cache/CacheItemPoolAdapterTest.php +++ b/test/Registry/Cache/CacheItemPoolAdapterTest.php @@ -4,8 +4,8 @@ namespace FlixTech\SchemaRegistryApi\Test\Registry\Cache; +use FlixTech\SchemaRegistryApi\Registry\Cache\CacheAdapter; use FlixTech\SchemaRegistryApi\Registry\Cache\CacheItemPoolAdapter; -use FlixTech\SchemaRegistryApi\Registry\CacheAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; class CacheItemPoolAdapterTest extends AbstractCacheAdapterTestCase diff --git a/test/Registry/Cache/DoctrineCacheAdapterTest.php b/test/Registry/Cache/DoctrineCacheAdapterTest.php index 541f944..c6d4a6e 100644 --- a/test/Registry/Cache/DoctrineCacheAdapterTest.php +++ b/test/Registry/Cache/DoctrineCacheAdapterTest.php @@ -5,8 +5,8 @@ namespace FlixTech\SchemaRegistryApi\Test\Registry\Cache; use Doctrine\Common\Cache\ArrayCache; +use FlixTech\SchemaRegistryApi\Registry\Cache\CacheAdapter; use FlixTech\SchemaRegistryApi\Registry\Cache\DoctrineCacheAdapter; -use FlixTech\SchemaRegistryApi\Registry\CacheAdapter; class DoctrineCacheAdapterTest extends AbstractCacheAdapterTestCase { diff --git a/test/Registry/Cache/SimpleCacheAdapterTest.php b/test/Registry/Cache/SimpleCacheAdapterTest.php index 0546185..1b9e0b9 100644 --- a/test/Registry/Cache/SimpleCacheAdapterTest.php +++ b/test/Registry/Cache/SimpleCacheAdapterTest.php @@ -4,8 +4,8 @@ namespace FlixTech\SchemaRegistryApi\Test\Registry\Cache; +use FlixTech\SchemaRegistryApi\Registry\Cache\CacheAdapter; use FlixTech\SchemaRegistryApi\Registry\Cache\SimpleCacheAdapter; -use FlixTech\SchemaRegistryApi\Registry\CacheAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Psr16Cache; diff --git a/test/Registry/BlockingRegistryTest.php b/test/Registry/Decorators/BlockingDecoratorTest.php similarity index 94% rename from test/Registry/BlockingRegistryTest.php rename to test/Registry/Decorators/BlockingDecoratorTest.php index 0f36b1b..02a7bce 100644 --- a/test/Registry/BlockingRegistryTest.php +++ b/test/Registry/Decorators/BlockingDecoratorTest.php @@ -2,20 +2,20 @@ declare(strict_types=1); -namespace FlixTech\SchemaRegistryApi\Test\Registry; +namespace FlixTech\SchemaRegistryApi\Test\Registry\Decorators; use AvroSchema; use AvroSchemaParseException; use FlixTech\SchemaRegistryApi\AsynchronousRegistry; use FlixTech\SchemaRegistryApi\Exception\SchemaRegistryException; -use FlixTech\SchemaRegistryApi\Registry\BlockingRegistry; +use FlixTech\SchemaRegistryApi\Registry\Decorators\BlockingDecorator; use FlixTech\SchemaRegistryApi\SynchronousRegistry; use GuzzleHttp\Promise\FulfilledPromise; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use RuntimeException; -class BlockingRegistryTest extends TestCase +class BlockingDecoratorTest extends TestCase { /** * @var AsynchronousRegistry|MockObject @@ -32,7 +32,7 @@ class BlockingRegistryTest extends TestCase protected function setUp(): void { $this->asyncRegistry = $this->getMockForAbstractClass(AsynchronousRegistry::class); - $this->blockingRegistry = new BlockingRegistry($this->asyncRegistry); + $this->blockingRegistry = new BlockingDecorator($this->asyncRegistry); } /** diff --git a/test/Registry/CachedRegistryTest.php b/test/Registry/Decorators/CachingDecoratorTest.php similarity index 96% rename from test/Registry/CachedRegistryTest.php rename to test/Registry/Decorators/CachingDecoratorTest.php index 2d7a702..182603f 100644 --- a/test/Registry/CachedRegistryTest.php +++ b/test/Registry/Decorators/CachingDecoratorTest.php @@ -2,21 +2,21 @@ declare(strict_types=1); -namespace FlixTech\SchemaRegistryApi\Test\Registry; +namespace FlixTech\SchemaRegistryApi\Test\Registry\Decorators; use AvroSchema; use AvroSchemaParseException; use FlixTech\SchemaRegistryApi\Exception\SchemaRegistryException; use FlixTech\SchemaRegistryApi\Exception\SubjectNotFoundException; use FlixTech\SchemaRegistryApi\Registry; -use FlixTech\SchemaRegistryApi\Registry\CacheAdapter; -use FlixTech\SchemaRegistryApi\Registry\CachedRegistry; +use FlixTech\SchemaRegistryApi\Registry\Cache\CacheAdapter; +use FlixTech\SchemaRegistryApi\Registry\Decorators\CachingDecorator; use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Promise\PromiseInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -class CachedRegistryTest extends TestCase +class CachingDecoratorTest extends TestCase { /** * @var Registry|MockObject @@ -29,7 +29,7 @@ class CachedRegistryTest extends TestCase private $cacheAdapter; /** - * @var CachedRegistry + * @var CachingDecorator */ private $cachedRegistry; @@ -61,7 +61,7 @@ protected function setUp(): void return md5((string) $schema); }; - $this->cachedRegistry = new CachedRegistry($this->registryMock, $this->cacheAdapter); + $this->cachedRegistry = new CachingDecorator($this->registryMock, $this->cacheAdapter); } /** @@ -234,7 +234,7 @@ public function it_should_accept_different_hash_algo_functions(): void return sha1((string) $schema); }; - $this->cachedRegistry = new CachedRegistry($this->registryMock, $this->cacheAdapter, $sha1HashFunction); + $this->cachedRegistry = new CachingDecorator($this->registryMock, $this->cacheAdapter, $sha1HashFunction); $this->registryMock ->expects(self::never()) diff --git a/test/Registry/PromisingRegistryTest.php b/test/Registry/GuzzlePromiseAsyncRegistryTest.php similarity index 64% rename from test/Registry/PromisingRegistryTest.php rename to test/Registry/GuzzlePromiseAsyncRegistryTest.php index 01e3171..5c2014b 100644 --- a/test/Registry/PromisingRegistryTest.php +++ b/test/Registry/GuzzlePromiseAsyncRegistryTest.php @@ -7,9 +7,13 @@ use AvroSchema; use AvroSchemaParseException; use Exception; +use FlixTech\SchemaRegistryApi\Constants; use FlixTech\SchemaRegistryApi\Exception\SchemaNotFoundException; use FlixTech\SchemaRegistryApi\Exception\SchemaRegistryException; -use FlixTech\SchemaRegistryApi\Registry\PromisingRegistry; +use FlixTech\SchemaRegistryApi\Registry\GuzzlePromiseAsyncRegistry; +use FlixTech\SchemaRegistryApi\Requests; +use FlixTech\SchemaRegistryApi\Schema\AvroName; +use FlixTech\SchemaRegistryApi\Schema\AvroReference; use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; @@ -18,19 +22,12 @@ use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use const FlixTech\SchemaRegistryApi\Constants\VERSION_LATEST; -use function FlixTech\SchemaRegistryApi\Requests\checkIfSubjectHasSchemaRegisteredRequest; -use function FlixTech\SchemaRegistryApi\Requests\registerNewSchemaVersionWithSubjectRequest; -use function FlixTech\SchemaRegistryApi\Requests\schemaRequest; -use function FlixTech\SchemaRegistryApi\Requests\singleSubjectVersionRequest; -use function FlixTech\SchemaRegistryApi\Requests\validateSchemaId; -use function FlixTech\SchemaRegistryApi\Requests\validateVersionId; - -class PromisingRegistryTest extends TestCase + +class GuzzlePromiseAsyncRegistryTest extends TestCase { /** - * @var PromisingRegistry + * @var GuzzlePromiseAsyncRegistry */ private $registry; @@ -46,15 +43,15 @@ public function it_should_register_schemas(): void ]; $subject = 'test'; $schema = AvroSchema::parse('{"type": "string"}'); - $expectedRequest = registerNewSchemaVersionWithSubjectRequest((string) $schema, $subject); + $references = new AvroReference(new AvroName('test.name'), 'example-value', Constants::VERSION_LATEST); + $expectedRequest = Requests::registerNewSchemaVersionWithSubjectRequest((string)$schema, $subject, $references); $container = []; - $this->registry = new PromisingRegistry($this->clientWithMockResponses($responses, $container)); + $client = $this->clientWithMockResponses($responses, $container); + + $this->registry = new GuzzlePromiseAsyncRegistry($client); - $promise = $this->registry->register( - $subject, - $schema - ); + $promise = $this->registry->register($subject, $schema, $references); self::assertEquals(3, $promise->wait()); $this->assertRequestCallable($expectedRequest)($container[0]['request']); @@ -72,15 +69,12 @@ public function it_can_get_the_schema_id_for_a_schema_and_subject(): void ]; $subject = 'test'; $schema = AvroSchema::parse('{"type": "string"}'); - $expectedRequest = checkIfSubjectHasSchemaRegisteredRequest($subject, (string) $schema); + $expectedRequest = Requests::checkIfSubjectHasSchemaRegisteredRequest($subject, (string)$schema); $container = []; - $this->registry = new PromisingRegistry($this->clientWithMockResponses($responses, $container)); + $this->registry = new GuzzlePromiseAsyncRegistry($this->clientWithMockResponses($responses, $container)); - $promise = $this->registry->schemaId( - $subject, - $schema, - ); + $promise = $this->registry->schemaId($subject, $schema); self::assertEquals(2, $promise->wait()); $this->assertRequestCallable($expectedRequest)($container[0]['request']); @@ -97,14 +91,12 @@ public function it_can_get_a_schema_for_id(): void new Response(200, [], '{"schema": "\"string\""}') ]; $schema = AvroSchema::parse('"string"'); - $expectedRequest = schemaRequest(validateSchemaId(1)); + $expectedRequest = Requests::schemaRequest(Requests::validateSchemaId(1)); $container = []; - $this->registry = new PromisingRegistry($this->clientWithMockResponses($responses, $container)); + $this->registry = new GuzzlePromiseAsyncRegistry($this->clientWithMockResponses($responses, $container)); - $promise = $this->registry->schemaForId( - 1, - ); + $promise = $this->registry->schemaForId(1); self::assertEquals($schema, $promise->wait()); $this->assertRequestCallable($expectedRequest)($container[0]['request']); @@ -123,15 +115,12 @@ public function it_can_get_a_schema_for_subject_and_version(): void $subject = 'test'; $version = 2; $schema = AvroSchema::parse('{"type": "string"}'); - $expectedRequest = singleSubjectVersionRequest($subject, validateVersionId($version)); + $expectedRequest = Requests::singleSubjectVersionRequest($subject, Requests::validateVersionId($version)); $container = []; - $this->registry = new PromisingRegistry($this->clientWithMockResponses($responses, $container)); + $this->registry = new GuzzlePromiseAsyncRegistry($this->clientWithMockResponses($responses, $container)); - $promise = $this->registry->schemaForSubjectAndVersion( - $subject, - $version, - ); + $promise = $this->registry->schemaForSubjectAndVersion($subject, $version); self::assertEquals($schema, $promise->wait()); $this->assertRequestCallable($expectedRequest)($container[0]['request']); @@ -149,15 +138,12 @@ public function it_can_get_the_schema_version(): void ]; $subject = 'test'; $schema = AvroSchema::parse('{"type": "string"}'); - $expectedRequest = checkIfSubjectHasSchemaRegisteredRequest($subject, (string) $schema); + $expectedRequest = Requests::checkIfSubjectHasSchemaRegisteredRequest($subject, (string)$schema); $container = []; - $this->registry = new PromisingRegistry($this->clientWithMockResponses($responses, $container)); + $this->registry = new GuzzlePromiseAsyncRegistry($this->clientWithMockResponses($responses, $container)); - $promise = $this->registry->schemaVersion( - $subject, - $schema, - ); + $promise = $this->registry->schemaVersion($subject, $schema); self::assertEquals(3, $promise->wait()); $this->assertRequestCallable($expectedRequest)($container[0]['request']); @@ -176,14 +162,12 @@ public function it_can_get_the_latest_version(): void $subject = 'test'; $schema = AvroSchema::parse('{"type": "string"}'); - $expectedRequest = singleSubjectVersionRequest($subject, VERSION_LATEST); + $expectedRequest = Requests::singleSubjectVersionRequest($subject, Constants::VERSION_LATEST); $container = []; - $this->registry = new PromisingRegistry($this->clientWithMockResponses($responses, $container)); + $this->registry = new GuzzlePromiseAsyncRegistry($this->clientWithMockResponses($responses, $container)); - $promise = $this->registry->latestVersion( - $subject, - ); + $promise = $this->registry->latestVersion($subject); self::assertEquals($schema, $promise->wait()); $this->assertRequestCallable($expectedRequest)($container[0]['request']); @@ -202,7 +186,8 @@ public function it_will_not_throw_but_pass_exceptions(): void sprintf('{"error_code": %d, "message": "test"}', SchemaNotFoundException::ERROR_CODE) ) ]; - $this->registry = new PromisingRegistry($this->clientWithMockResponses($responses)); + + $this->registry = new GuzzlePromiseAsyncRegistry($this->clientWithMockResponses($responses)); /** @var Exception $exception */ $exception = $this->registry->schemaForId(1)->wait(); @@ -213,24 +198,27 @@ public function it_will_not_throw_but_pass_exceptions(): void /** * @param ResponseInterface[] $responses - * @param array $container + * @param array $container * * @return Client */ private function clientWithMockResponses(array $responses, array &$container = []): Client { + $history = Middleware::history($container); + $mockHandler = new MockHandler($responses); $stack = HandlerStack::create($mockHandler); - $stack->push(Middleware::history($container)); + $stack->push($history); return new Client(['handler' => $stack]); } private function assertRequestCallable(RequestInterface $expectedRequest): callable { - return function (RequestInterface $actual) use ($expectedRequest) { + return function (RequestInterface $actual) use ($expectedRequest) { $this->assertEquals($expectedRequest->getUri(), $actual->getUri()); - //$this->assertEquals($expectedRequest->getHeaders(), $actual->getHeaders()); + $this->assertEquals($expectedRequest->getHeader(Constants::ACCEPT), $actual->getHeader(Constants::ACCEPT)); + $this->assertEquals($expectedRequest->getHeader(Constants::CONTENT_TYPE), $actual->getHeader(Constants::CONTENT_TYPE)); $this->assertEquals($expectedRequest->getMethod(), $actual->getMethod()); $this->assertEquals($expectedRequest->getBody()->getContents(), $actual->getBody()->getContents()); diff --git a/test/Registry/Psr18SyncRegistryTest.php b/test/Registry/Psr18SyncRegistryTest.php new file mode 100644 index 0000000..67a88a0 --- /dev/null +++ b/test/Registry/Psr18SyncRegistryTest.php @@ -0,0 +1,210 @@ +registry = new Psr18SyncRegistry($this->clientWithMockResponses($responses, $container)); + + self::assertEquals(3, $this->registry->register($subject, $schema, $references)); + $this->assertRequestCallable($expectedRequest)($container[0]['request']); + } + + /** + * @test + * @throws SchemaRegistryException + * @throws AvroSchemaParseException + */ + public function it_can_get_the_schema_id_for_a_schema_and_subject(): void + { + $responses = [ + new Response(200, [], '{"id": 2}') + ]; + $subject = 'test'; + $schema = AvroSchema::parse('{"type": "string"}'); + $expectedRequest = Requests::checkIfSubjectHasSchemaRegisteredRequest($subject, (string)$schema); + + $container = []; + $this->registry = new Psr18SyncRegistry($this->clientWithMockResponses($responses, $container)); + + self::assertEquals(2, $this->registry->schemaId($subject, $schema)); + $this->assertRequestCallable($expectedRequest)($container[0]['request']); + } + + /** + * @test + * @throws SchemaRegistryException + * @throws AvroSchemaParseException + */ + public function it_can_get_a_schema_for_id(): void + { + $responses = [ + new Response(200, [], '{"schema": "\"string\""}') + ]; + $schema = AvroSchema::parse('"string"'); + $expectedRequest = Requests::schemaRequest(Requests::validateSchemaId(1)); + + $container = []; + $this->registry = new Psr18SyncRegistry($this->clientWithMockResponses($responses, $container)); + + self::assertEquals($schema, $this->registry->schemaForId(1)); + $this->assertRequestCallable($expectedRequest)($container[0]['request']); + } + + /** + * @test + * @throws SchemaRegistryException + * @throws AvroSchemaParseException + */ + public function it_can_get_a_schema_for_subject_and_version(): void + { + $responses = [ + new Response(200, [], '{"schema": "\"string\""}') + ]; + $subject = 'test'; + $version = 2; + $schema = AvroSchema::parse('{"type": "string"}'); + $expectedRequest = Requests::singleSubjectVersionRequest($subject, Requests::validateVersionId($version)); + + $container = []; + $this->registry = new Psr18SyncRegistry($this->clientWithMockResponses($responses, $container)); + + self::assertEquals($schema, $this->registry->schemaForSubjectAndVersion($subject, $version)); + $this->assertRequestCallable($expectedRequest)($container[0]['request']); + } + + /** + * @test + * @throws SchemaRegistryException + * @throws AvroSchemaParseException + */ + public function it_can_get_the_schema_version(): void + { + $responses = [ + new Response(200, [], '{"version": 3}') + ]; + $subject = 'test'; + $schema = AvroSchema::parse('{"type": "string"}'); + $expectedRequest = Requests::checkIfSubjectHasSchemaRegisteredRequest($subject, (string)$schema); + + $container = []; + $this->registry = new Psr18SyncRegistry($this->clientWithMockResponses($responses, $container)); + + self::assertEquals(3, $this->registry->schemaVersion($subject, $schema)); + $this->assertRequestCallable($expectedRequest)($container[0]['request']); + } + + /** + * @test + * @throws SchemaRegistryException + * @throws AvroSchemaParseException + */ + public function it_can_get_the_latest_version(): void + { + $responses = [ + new Response(200, [], '{"schema": "\"string\""}') + ]; + + $subject = 'test'; + $schema = AvroSchema::parse('{"type": "string"}'); + $expectedRequest = Requests::singleSubjectVersionRequest($subject, Constants::VERSION_LATEST); + + $container = []; + $this->registry = new Psr18SyncRegistry($this->clientWithMockResponses($responses, $container)); + + self::assertEquals($schema, $this->registry->latestVersion($subject)); + $this->assertRequestCallable($expectedRequest)($container[0]['request']); + } + + /** + * @test + * @throws SchemaRegistryException + */ + public function it_will_throw_exceptions(): void + { + $this->expectException(SchemaNotFoundException::class); + + $responses = [ + new Response( + 404, + [], + sprintf('{"error_code": %d, "message": "test"}', SchemaNotFoundException::ERROR_CODE) + ) + ]; + + $this->registry = new Psr18SyncRegistry($this->clientWithMockResponses($responses)); + + $this->registry->schemaForId(1); + } + + /** + * @param ResponseInterface[] $responses + * @param array $container + * + * @return Client + */ + private function clientWithMockResponses(array $responses, array &$container = []): Client + { + $history = Middleware::history($container); + + $mockHandler = new MockHandler($responses); + $stack = HandlerStack::create($mockHandler); + $stack->push($history); + + return new Client(['handler' => $stack]); + } + + private function assertRequestCallable(RequestInterface $expectedRequest): callable + { + return function (RequestInterface $actual) use ($expectedRequest) { + $this->assertEquals($expectedRequest->getUri(), $actual->getUri()); + $this->assertEquals($expectedRequest->getHeader(Constants::ACCEPT), $actual->getHeader(Constants::ACCEPT)); + $this->assertEquals($expectedRequest->getHeader(Constants::CONTENT_TYPE), $actual->getHeader(Constants::CONTENT_TYPE)); + $this->assertEquals($expectedRequest->getMethod(), $actual->getMethod()); + $this->assertEquals($expectedRequest->getBody()->getContents(), $actual->getBody()->getContents()); + + return $actual; + }; + } +} diff --git a/test/Requests/FunctionsTest.php b/test/Requests/FunctionsTest.php index 30f1e55..14ced3c 100644 --- a/test/Requests/FunctionsTest.php +++ b/test/Requests/FunctionsTest.php @@ -4,13 +4,10 @@ namespace FlixTech\SchemaRegistryApi\Test\Requests; -use FlixTech\SchemaRegistryApi\Schema\AvroName; -use FlixTech\SchemaRegistryApi\Schema\AvroReference; -use Generator; use InvalidArgumentException; use PHPUnit\Framework\TestCase; +use const FlixTech\SchemaRegistryApi\Constants\ACCEPT; use const FlixTech\SchemaRegistryApi\Constants\ACCEPT_HEADER; -use const FlixTech\SchemaRegistryApi\Constants\ACCEPT_HEADER_KEY; use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_BACKWARD; use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_BACKWARD_TRANSITIVE; use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_FORWARD; @@ -18,8 +15,8 @@ use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_FULL; use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_FULL_TRANSITIVE; use const FlixTech\SchemaRegistryApi\Constants\COMPATIBILITY_NONE; +use const FlixTech\SchemaRegistryApi\Constants\CONTENT_TYPE; use const FlixTech\SchemaRegistryApi\Constants\CONTENT_TYPE_HEADER; -use const FlixTech\SchemaRegistryApi\Constants\CONTENT_TYPE_HEADER_KEY; use const FlixTech\SchemaRegistryApi\Constants\VERSION_LATEST; use function FlixTech\SchemaRegistryApi\Requests\allSubjectsRequest; use function FlixTech\SchemaRegistryApi\Requests\allSubjectVersionsRequest; @@ -41,6 +38,9 @@ use function FlixTech\SchemaRegistryApi\Requests\validateSchemaStringAsJson; use function FlixTech\SchemaRegistryApi\Requests\validateVersionId; +/** + * @deprecated Use \FlixTech\SchemaRegistryApi\Test\Requests instead + */ class FunctionsTest extends TestCase { /** @@ -52,7 +52,7 @@ public function it_should_produce_a_Request_to_get_all_subjects(): void self::assertEquals('GET', $request->getMethod()); self::assertEquals('subjects', $request->getUri()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); } /** @@ -64,7 +64,7 @@ public function it_should_produce_a_Request_to_get_all_subject_versions(): void self::assertEquals('GET', $request->getMethod()); self::assertEquals('subjects/test/versions', $request->getUri()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); } /** @@ -76,59 +76,33 @@ public function it_should_produce_a_Request_to_get_a_specific_subject_version(): self::assertEquals('GET', $request->getMethod()); self::assertEquals('subjects/test/versions/3', $request->getUri()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); } /** * @test - * - * @param string $initialSchema - * @param string $finalSchema - * @param AvroReference[] $references - * @dataProvider dataForRegisteringSchemas */ - public function it_should_produce_a_request_to_register_a_new_schema_version(string $initialSchema, string $finalSchema, array $references): void + public function it_should_produce_a_request_to_register_a_new_schema_version(): void { - $request = registerNewSchemaVersionWithSubjectRequest($initialSchema, 'test', ...$references); + $request = registerNewSchemaVersionWithSubjectRequest('{"type": "string"}', 'test'); self::assertEquals('POST', $request->getMethod()); self::assertEquals('subjects/test/versions', $request->getUri()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); - self::assertEquals(CONTENT_TYPE_HEADER[CONTENT_TYPE_HEADER_KEY], $request->getHeader(CONTENT_TYPE_HEADER_KEY)[0]); - self::assertJsonStringEqualsJsonString($finalSchema, $request->getBody()->getContents()); - } + self::assertEquals( + [CONTENT_TYPE => [CONTENT_TYPE_HEADER[CONTENT_TYPE]]] + [ACCEPT => [ACCEPT_HEADER[ACCEPT]]], + $request->getHeaders() + ); + self::assertEquals('{"schema":"{\"type\":\"string\"}"}', $request->getBody()->getContents()); - public static function dataForRegisteringSchemas(): Generator { - yield 'Schema without schema key' => [ - '{"type":"string"}', - '{"schema":"{\"type\":\"string\"}"}', - [], - ]; + $request = registerNewSchemaVersionWithSubjectRequest('{"schema": "{\"type\": \"string\"}"}', 'test'); - yield 'Schema without schema key and references' => [ - '{"type":"string"}', - /** @lang JSON */<<getMethod()); + self::assertEquals('subjects/test/versions', $request->getUri()); + self::assertEquals( + [CONTENT_TYPE => [CONTENT_TYPE_HEADER[CONTENT_TYPE]]] + [ACCEPT => [ACCEPT_HEADER[ACCEPT]]], + $request->getHeaders() + ); + self::assertEquals('{"schema":"{\"type\": \"string\"}"}', $request->getBody()->getContents()); } /** @@ -145,8 +119,10 @@ public function it_should_produce_a_request_to_check_schema_compatibility_agains self::assertEquals('POST', $request->getMethod()); self::assertEquals('compatibility/subjects/test/versions/latest', $request->getUri()); self::assertEquals('{"schema":"{\"type\":\"test\"}"}', $request->getBody()->getContents()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); - self::assertEquals(CONTENT_TYPE_HEADER[CONTENT_TYPE_HEADER_KEY], $request->getHeader(CONTENT_TYPE_HEADER_KEY)[0]); + self::assertEquals( + [CONTENT_TYPE => [CONTENT_TYPE_HEADER[CONTENT_TYPE]]] + [ACCEPT => [ACCEPT_HEADER[ACCEPT]]], + $request->getHeaders() + ); } /** @@ -159,8 +135,10 @@ public function it_should_produce_a_request_to_check_if_a_subject_already_has_a_ self::assertEquals('POST', $request->getMethod()); self::assertEquals('subjects/test', $request->getUri()); self::assertEquals('{"schema":"{\"type\":\"test\"}"}', $request->getBody()->getContents()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); - self::assertEquals(CONTENT_TYPE_HEADER[CONTENT_TYPE_HEADER_KEY], $request->getHeader(CONTENT_TYPE_HEADER_KEY)[0]); + self::assertEquals( + [CONTENT_TYPE => [CONTENT_TYPE_HEADER[CONTENT_TYPE]]] + [ACCEPT => [ACCEPT_HEADER[ACCEPT]]], + $request->getHeaders() + ); } /** @@ -172,7 +150,7 @@ public function it_should_produce_a_request_to_get_a_specific_schema_by_id(): vo self::assertEquals('GET', $request->getMethod()); self::assertEquals('schemas/ids/3', $request->getUri()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); } /** @@ -184,7 +162,7 @@ public function it_should_produce_a_request_to_get_the_global_compatibility_leve self::assertEquals('GET', $request->getMethod()); self::assertEquals('config', $request->getUri()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); } /** @@ -197,7 +175,7 @@ public function it_should_produce_a_request_to_change_the_global_compatibility_l self::assertEquals('PUT', $request->getMethod()); self::assertEquals('config', $request->getUri()); self::assertEquals('{"compatibility":"FULL"}', $request->getBody()->getContents()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); } /** @@ -209,7 +187,7 @@ public function it_should_produce_a_request_to_get_the_subject_compatibility_lev self::assertEquals('GET', $request->getMethod()); self::assertEquals('config/test', $request->getUri()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); } /** @@ -222,7 +200,7 @@ public function it_should_produce_a_request_to_change_the_subject_compatibility_ self::assertEquals('PUT', $request->getMethod()); self::assertEquals('config/test', $request->getUri()); self::assertEquals('{"compatibility":"FORWARD"}', $request->getBody()->getContents()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); } /** @@ -245,7 +223,7 @@ public function it_should_prepare_a_JSON_schema_for_transfer(): void { self::assertJsonStringEqualsJsonString( '{"schema":"{\"type\":\"string\"}"}', - prepareJsonSchemaForTransfer('{"type":"string"}') + prepareJsonSchemaForTransfer('{"type": "string"}') ); } @@ -385,7 +363,7 @@ public function it_should_produce_a_valid_subject_deletion_request(): void self::assertEquals('DELETE', $request->getMethod()); self::assertEquals('subjects/test', $request->getUri()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); } /** @@ -397,12 +375,12 @@ public function it_should_produce_a_valid_subject_version_deletion_request(): vo self::assertEquals('DELETE', $request->getMethod()); self::assertEquals('subjects/test/versions/latest', $request->getUri()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); $request = deleteSubjectVersionRequest('test', '5'); self::assertEquals('DELETE', $request->getMethod()); self::assertEquals('subjects/test/versions/5', $request->getUri()); - self::assertEquals(ACCEPT_HEADER[ACCEPT_HEADER_KEY], $request->getHeader(ACCEPT_HEADER_KEY)[0]); + self::assertEquals([ACCEPT => [ACCEPT_HEADER[ACCEPT]]], $request->getHeaders()); } } diff --git a/test/RequestsTest.php b/test/RequestsTest.php new file mode 100644 index 0000000..0b41d6a --- /dev/null +++ b/test/RequestsTest.php @@ -0,0 +1,411 @@ +getMethod()); + self::assertEquals('subjects', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + } + + /** + * @test + */ + public function it_should_produce_a_Request_to_get_all_subject_versions(): void + { + $request = Requests::allSubjectVersionsRequest('test'); + + self::assertEquals('GET', $request->getMethod()); + self::assertEquals('subjects/test/versions', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + } + + /** + * @test + */ + public function it_should_produce_a_Request_to_get_a_specific_subject_version(): void + { + $request = Requests::singleSubjectVersionRequest('test', '3'); + + self::assertEquals('GET', $request->getMethod()); + self::assertEquals('subjects/test/versions/3', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + } + + /** + * @test + * + * @param string $initialSchema + * @param string $finalSchema + * @param AvroReference[] $references + * @dataProvider dataForRegisteringSchemas + */ + public function it_should_produce_a_request_to_register_a_new_schema_version(string $initialSchema, string $finalSchema, array $references): void + { + $request = Requests::registerNewSchemaVersionWithSubjectRequest($initialSchema, 'test', ...$references); + + self::assertEquals('POST', $request->getMethod()); + self::assertEquals('subjects/test/versions', $request->getUri()); + self::assertEquals( + [Constants::CONTENT_TYPE => [Constants::CONTENT_TYPE_HEADER[Constants::CONTENT_TYPE]]] + [Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], + $request->getHeaders() + ); + self::assertJsonStringEqualsJsonString($finalSchema, $request->getBody()->getContents()); + } + + public static function dataForRegisteringSchemas(): Generator + { + yield 'Schema without schema key' => [ + '{"type":"string"}', + '{"schema":"{\"type\":\"string\"}"}', + [], + ]; + + yield 'Schema without schema key and references' => [ + '{"type":"string"}', + /** @lang JSON */<<getMethod()); + self::assertEquals('compatibility/subjects/test/versions/latest', $request->getUri()); + self::assertEquals('{"schema":"{\"type\":\"test\"}"}', $request->getBody()->getContents()); + self::assertEquals( + [Constants::CONTENT_TYPE => [Constants::CONTENT_TYPE_HEADER[Constants::CONTENT_TYPE]]] + [Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], + $request->getHeaders() + ); + } + + /** + * @test + */ + public function it_should_produce_a_request_to_check_if_a_subject_already_has_a_schema(): void + { + $request = Requests::checkIfSubjectHasSchemaRegisteredRequest('test', '{"type":"test"}'); + + self::assertEquals('POST', $request->getMethod()); + self::assertEquals('subjects/test', $request->getUri()); + self::assertEquals('{"schema":"{\"type\":\"test\"}"}', $request->getBody()->getContents()); + self::assertEquals( + [Constants::CONTENT_TYPE => [Constants::CONTENT_TYPE_HEADER[Constants::CONTENT_TYPE]]] + [Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], + $request->getHeaders() + ); + } + + /** + * @test + */ + public function it_should_produce_a_request_to_get_a_specific_schema_by_id(): void + { + $request = Requests::schemaRequest('3'); + + self::assertEquals('GET', $request->getMethod()); + self::assertEquals('schemas/ids/3', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + } + + /** + * @test + */ + public function it_should_produce_a_request_to_get_the_global_compatibility_level(): void + { + $request = Requests::defaultCompatibilityLevelRequest(); + + self::assertEquals('GET', $request->getMethod()); + self::assertEquals('config', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + } + + /** + * @test + */ + public function it_should_produce_a_request_to_change_the_global_compatibility_level(): void + { + $request = Requests::changeDefaultCompatibilityLevelRequest(Constants::COMPATIBILITY_FULL); + + self::assertEquals('PUT', $request->getMethod()); + self::assertEquals('config', $request->getUri()); + self::assertEquals('{"compatibility":"FULL"}', $request->getBody()->getContents()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + } + + /** + * @test + */ + public function it_should_produce_a_request_to_get_the_subject_compatibility_level(): void + { + $request = Requests::subjectCompatibilityLevelRequest('test'); + + self::assertEquals('GET', $request->getMethod()); + self::assertEquals('config/test', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + } + + /** + * @test + */ + public function it_should_produce_a_request_to_change_the_subject_compatibility_level(): void + { + $request = Requests::changeSubjectCompatibilityLevelRequest('test', Constants::COMPATIBILITY_FORWARD); + + self::assertEquals('PUT', $request->getMethod()); + self::assertEquals('config/test', $request->getUri()); + self::assertEquals('{"compatibility":"FORWARD"}', $request->getBody()->getContents()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + } + + /** + * @test + */ + public function it_should_validate_a_JSON_schema_string(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$schema must be a valid JSON string'); + + self::assertJsonStringEqualsJsonString('{"type":"test"}', Json::validateStringAsJson('{"type":"test"}')); + + Json::validateStringAsJson('INVALID'); + } + + /** + * @test + */ + public function it_should_prepare_a_JSON_schema_for_transfer(): void + { + self::assertJsonStringEqualsJsonString( + '{"schema":"{\"type\":\"string\"}"}', + Requests::prepareJsonSchemaForTransfer('{"type":"string"}') + ); + } + + /** + * @test + */ + public function it_should_validate_a_compatibility_level_string(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$level must be one of NONE, BACKWARD, BACKWARD_TRANSITIVE, FORWARD, FORWARD_TRANSITIVE, FULL, FULL_TRANSITIVE'); + + self::assertEquals( + Constants::COMPATIBILITY_NONE, + Requests::validateCompatibilityLevel(Constants::COMPATIBILITY_NONE) + ); + self::assertEquals( + Constants::COMPATIBILITY_FULL, + Requests::validateCompatibilityLevel(Constants::COMPATIBILITY_FULL) + ); + self::assertEquals( + Constants::COMPATIBILITY_FULL_TRANSITIVE, + Requests::validateCompatibilityLevel(Constants::COMPATIBILITY_FULL_TRANSITIVE) + ); + self::assertEquals( + Constants::COMPATIBILITY_BACKWARD, + Requests::validateCompatibilityLevel(Constants::COMPATIBILITY_BACKWARD) + ); + self::assertEquals( + Constants::COMPATIBILITY_BACKWARD_TRANSITIVE, + Requests::validateCompatibilityLevel(Constants::COMPATIBILITY_BACKWARD_TRANSITIVE) + ); + self::assertEquals( + Constants::COMPATIBILITY_FORWARD, + Requests::validateCompatibilityLevel(Constants::COMPATIBILITY_FORWARD) + ); + self::assertEquals( + Constants::COMPATIBILITY_FORWARD_TRANSITIVE, + Requests::validateCompatibilityLevel(Constants::COMPATIBILITY_FORWARD_TRANSITIVE) + ); + + Requests::validateCompatibilityLevel('INVALID'); + } + + /** + * @test + */ + public function it_should_prepare_compatibility_string_for_transport(): void + { + self::assertEquals( + '{"compatibility":"NONE"}', + Requests::prepareCompatibilityLevelForTransport(Constants::COMPATIBILITY_NONE) + ); + self::assertEquals( + '{"compatibility":"BACKWARD"}', + Requests::prepareCompatibilityLevelForTransport(Constants::COMPATIBILITY_BACKWARD) + ); + self::assertEquals( + '{"compatibility":"BACKWARD_TRANSITIVE"}', + Requests::prepareCompatibilityLevelForTransport(Constants::COMPATIBILITY_BACKWARD_TRANSITIVE) + ); + self::assertEquals( + '{"compatibility":"FORWARD"}', + Requests::prepareCompatibilityLevelForTransport(Constants::COMPATIBILITY_FORWARD) + ); + self::assertEquals( + '{"compatibility":"FORWARD_TRANSITIVE"}', + Requests::prepareCompatibilityLevelForTransport(Constants::COMPATIBILITY_FORWARD_TRANSITIVE) + ); + self::assertEquals( + '{"compatibility":"FULL"}', + Requests::prepareCompatibilityLevelForTransport(Constants::COMPATIBILITY_FULL) + ); + self::assertEquals( + '{"compatibility":"FULL_TRANSITIVE"}', + Requests::prepareCompatibilityLevelForTransport(Constants::COMPATIBILITY_FULL_TRANSITIVE) + ); + } + + /** + * @test + */ + public function it_should_validate_version_id_type(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$versionId must be an integer of type int or string'); + + Requests::validateVersionId([3]); + } + + /** + * @test + */ + public function it_should_validate_version_id_overflow(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$versionId must be between 1 and 2^31 - 1'); + + Requests::validateVersionId(2 ** 31); + } + + /** + * @test + */ + public function it_should_validate_version_id_less_than_one(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('$versionId must be between 1 and 2^31 - 1'); + + Requests::validateVersionId(0); + } + + /** + * @test + */ + public function it_should_validate_valid_version_id(): void + { + self::assertSame(Constants::VERSION_LATEST, Requests::validateVersionId(Constants::VERSION_LATEST)); + self::assertSame('3', Requests::validateVersionId(3)); + self::assertSame('3', Requests::validateVersionId('3')); + } + + /** + * @test + */ + public function it_should_validate_valid_schema_ids(): void + { + self::assertSame('3', Requests::validateSchemaId(3)); + self::assertSame('3', Requests::validateSchemaId('3')); + } + + /** + * @test + */ + public function it_should_produce_a_valid_subject_deletion_request(): void + { + $request = Requests::deleteSubjectRequest('test'); + + self::assertEquals('DELETE', $request->getMethod()); + self::assertEquals('subjects/test?permanent=false', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + + $request = Requests::deleteSubjectRequest('test', false); + + self::assertEquals('DELETE', $request->getMethod()); + self::assertEquals('subjects/test?permanent=false', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + + $request = Requests::deleteSubjectRequest('test', true); + + self::assertEquals('DELETE', $request->getMethod()); + self::assertEquals('subjects/test?permanent=true', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + } + + /** + * @test + */ + public function it_should_produce_a_valid_subject_version_deletion_request(): void + { + $request = Requests::deleteSubjectVersionRequest('test', Constants::VERSION_LATEST, false); + + self::assertEquals('DELETE', $request->getMethod()); + self::assertEquals('subjects/test/versions/latest?permanent=false', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + + $request = Requests::deleteSubjectVersionRequest('test', Constants::VERSION_LATEST); + + self::assertEquals('DELETE', $request->getMethod()); + self::assertEquals('subjects/test/versions/latest?permanent=false', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + + $request = Requests::deleteSubjectVersionRequest('test', '5', false); + + self::assertEquals('DELETE', $request->getMethod()); + self::assertEquals('subjects/test/versions/5?permanent=false', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + + $request = Requests::deleteSubjectVersionRequest('test', '5', true); + + self::assertEquals('DELETE', $request->getMethod()); + self::assertEquals('subjects/test/versions/5?permanent=true', $request->getUri()); + self::assertEquals([Constants::ACCEPT => [Constants::ACCEPT_HEADER[Constants::ACCEPT]]], $request->getHeaders()); + } +} diff --git a/test/Schema/AvroNameTest.php b/test/Schema/AvroNameTest.php index d75d926..1ca73d4 100644 --- a/test/Schema/AvroNameTest.php +++ b/test/Schema/AvroNameTest.php @@ -15,7 +15,8 @@ class AvroNameTest extends TestCase * @dataProvider avroReferences * @test */ - public function it_should_only_be_constructable_from_a_valid_Avro_reference(string $fullName, bool $isValid): void { + public function it_should_only_be_constructable_from_a_valid_Avro_reference(string $fullName, bool $isValid): void + { if (!$isValid) { $this->expectException(InvalidArgumentException::class); } @@ -23,7 +24,8 @@ public function it_should_only_be_constructable_from_a_valid_Avro_reference(stri $this->assertSame((string) new AvroName($fullName), $fullName); } - public static function avroReferences(): Generator { + public static function avroReferences(): Generator + { yield 'Valid root name' => ['test', true]; yield 'Valid full name' => ['test.example', true]; yield 'Empty full name' => ['', false]; diff --git a/test/Schema/AvroReferenceTest.php b/test/Schema/AvroReferenceTest.php index c3fe852..951c01d 100644 --- a/test/Schema/AvroReferenceTest.php +++ b/test/Schema/AvroReferenceTest.php @@ -19,9 +19,10 @@ class AvroReferenceTest extends TestCase * @param string $subject * @param string|int $version * @param bool $isValid - * @param string $expectedJson + * @param ?string $expectedJson */ - public function it_should_be_constructable(string $avroName, string $subject, $version, bool $isValid, ?string $expectedJson): void { + public function it_should_be_constructable(string $avroName, string $subject, $version, bool $isValid, ?string $expectedJson): void + { if (!$isValid) { $this->expectException(InvalidArgumentException::class); } @@ -32,7 +33,8 @@ public function it_should_be_constructable(string $avroName, string $subject, $v ); } - public static function references(): Generator { + public static function references(): Generator + { yield 'Valid version with latest' => [ 'test.example.MyRecord', 'example-value', @@ -68,6 +70,5 @@ public static function references(): Generator { false, null, ]; - } } diff --git a/test/Schemas/SchemaTypeTest.php b/test/Schemas/SchemaTypeTest.php new file mode 100644 index 0000000..5c66d38 --- /dev/null +++ b/test/Schemas/SchemaTypeTest.php @@ -0,0 +1,63 @@ + $className + * @param string $expected + */ + public function type_should_match_the_value(string $className, string $expected): void + { + self::assertEquals($expected, $className::instance()->value()); + self::assertEquals($expected, (string)$className::instance()); + } + + /** + * @test + * @dataProvider provideSchemaTypes + * + * @phpstan-template template T of SchemaType + * @param string $className + * @phpstan-param class-string $className + */ + public function types_cannot_be_cloned(string $className): void + { + $this->expectException(Error::class); + $result = clone $className::instance(); + } + + public function provideSchemaTypes(): Generator + { + yield 'AvroSchemaType' => [ + AvroSchemaType::class, + Constants::AVRO_TYPE, + ]; + + yield 'JsonSchemaType' => [ + JsonSchemaType::class, + Constants::JSON_TYPE, + ]; + + yield 'ProtobufSchemaType' => [ + ProtobufSchemaType::class, + Constants::PROTOBUF_TYPE, + ]; + } +}