From ac48f18f9127ca794ad48daefe19258b3968e0fa Mon Sep 17 00:00:00 2001 From: Jasper Zonneveld Date: Wed, 17 Jul 2019 17:40:54 +0200 Subject: [PATCH] Remove art4/json-api-client dependency The dependency had some major changes and it was a lot of work to update the package. We can easily validate and parse the json ourselves, so I just removed the dependency. Besides, this dependency gave licensing issues because it is licensed under GPL. --- CHANGELOG.md | 4 +- README.MD | 6 +- composer.json | 1 - src/Exceptions/Exception.php | 7 + src/Exceptions/ValidationException.php | 7 + src/Interfaces/ItemInterface.php | 7 + src/Item.php | 2 +- src/Links.php | 4 +- src/Parsers/CollectionParser.php | 15 +- src/Parsers/DocumentParser.php | 176 +++--- src/Parsers/ErrorCollectionParser.php | 49 ++ src/Parsers/ErrorParser.php | 94 ++++ src/Parsers/ErrorsParser.php | 108 ---- src/Parsers/ItemParser.php | 131 +++-- src/Parsers/JsonapiParser.php | 29 +- src/Parsers/LinksParser.php | 38 +- src/Parsers/MetaParser.php | 12 +- tests/Parsers/CollectionParserTest.php | 71 ++- tests/Parsers/DocumentParserTest.php | 75 ++- tests/Parsers/ErrorCollectionParserTest.php | 92 ++++ tests/Parsers/ErrorParserTest.php | 314 +++++++++++ tests/Parsers/ErrorsParserTest.php | 105 ---- tests/Parsers/ItemParserTest.php | 570 +++++++++++++++----- tests/Parsers/JsonapiParserTest.php | 80 ++- tests/Parsers/LinksParserTest.php | 80 ++- tests/Parsers/MetaParserTest.php | 47 +- 26 files changed, 1506 insertions(+), 618 deletions(-) create mode 100644 src/Exceptions/Exception.php create mode 100644 src/Exceptions/ValidationException.php create mode 100644 src/Parsers/ErrorCollectionParser.php create mode 100644 src/Parsers/ErrorParser.php delete mode 100644 src/Parsers/ErrorsParser.php create mode 100644 tests/Parsers/ErrorCollectionParserTest.php create mode 100644 tests/Parsers/ErrorParserTest.php delete mode 100644 tests/Parsers/ErrorsParserTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f191e..2a9f60e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -* Nothing +### Changed + +* Drop art4/json-api-client dependency and validate the JSON ourselves. ## [0.20.0] - 2019-07-11 diff --git a/README.MD b/README.MD index 369164c..9fdd062 100644 --- a/README.MD +++ b/README.MD @@ -157,7 +157,7 @@ This package uses [Laravel Collections](https://laravel.com/docs/collections) as ## Links All objects that can have links (i.e. document, error, item and relationship) use `Concerns/HasLinks` and thus have a `getLinks` method that returns an instance of `Links`. -This is a simple array-like object with key-value pairs which are in turn an instance of `Link`. +This is a simple array-like object with key-value pairs which are in turn an instance of `Link` or `null`. ### Example @@ -432,7 +432,7 @@ If a response does not have a successful status code (2xx) and does not have a b #### Non 2xx request with invalid JSON:API body If a response does not have a successful status code (2xx) and does have a body, it is parsed as if it's a JSON:API document. -If the response can not be parsed as such document, a `\Art4\JsonApiClient\Exception\ValidationException` will be thrown. +If the response can not be parsed as such document, a `ValidationException` will be thrown. #### Non 2xx request with valid JSON:API body @@ -466,7 +466,7 @@ This can be a plain `Document` when there is no data, an `ItemDocument` for an i The `DocumentClient` follows the following steps internally: 1. Send the request using your HTTP client; - 2. Use [art4/json-api-client](https://github.com/art4/json-api-client) to parse and validate the response; + 2. Use `ResponseParser` to parse and validate the response; 3. Create the correct document instance; 4. Hydrate every item by using the item model registered with the `TypeMapper` or a `\Swis\JsonApi\Client\Item` as fallback; 5. Hydrate all relationships; diff --git a/composer.json b/composer.json index 7624373..ea7cfb6 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,6 @@ "require": { "php": ">=7.1.3", "ext-json": "*", - "art4/json-api-client": "^0.9.1", "illuminate/support": "5.5.*|5.6.*|5.7.*|5.8.*", "jenssegers/model": "^1.1", "php-http/client-implementation": "^1.0", diff --git a/src/Exceptions/Exception.php b/src/Exceptions/Exception.php new file mode 100644 index 0000000..8cf95ea --- /dev/null +++ b/src/Exceptions/Exception.php @@ -0,0 +1,7 @@ +toArray(); + static function (?Link $link) { + return $link ? $link->toArray() : null; }, $this->links ); diff --git a/src/Parsers/CollectionParser.php b/src/Parsers/CollectionParser.php index 105f71f..ede0499 100644 --- a/src/Parsers/CollectionParser.php +++ b/src/Parsers/CollectionParser.php @@ -2,9 +2,8 @@ namespace Swis\JsonApi\Client\Parsers; -use Art4\JsonApiClient\ResourceCollectionInterface; -use Art4\JsonApiClient\ResourceItemInterface; use Swis\JsonApi\Client\Collection; +use Swis\JsonApi\Client\Exceptions\ValidationException; /** * @internal @@ -25,15 +24,19 @@ public function __construct(ItemParser $itemParser) } /** - * @param \Art4\JsonApiClient\ResourceCollectionInterface $jsonApiCollection + * @param mixed $data * * @return \Swis\JsonApi\Client\Collection */ - public function parse(ResourceCollectionInterface $jsonApiCollection): Collection + public function parse($data): Collection { - return Collection::make($jsonApiCollection->asArray()) + if (!is_array($data)) { + throw new ValidationException(sprintf('ResourceCollection has to be an array, "%s" given.', gettype($data))); + } + + return Collection::make($data) ->map( - function (ResourceItemInterface $item) { + function ($item) { return $this->itemParser->parse($item); } ); diff --git a/src/Parsers/DocumentParser.php b/src/Parsers/DocumentParser.php index 2f03df6..f61fad8 100644 --- a/src/Parsers/DocumentParser.php +++ b/src/Parsers/DocumentParser.php @@ -2,31 +2,19 @@ namespace Swis\JsonApi\Client\Parsers; -use Art4\JsonApiClient\DocumentInterface as Art4JsonApiDocumentInterface; -use Art4\JsonApiClient\ResourceCollectionInterface; -use Art4\JsonApiClient\ResourceItemInterface; -use Art4\JsonApiClient\Utils\Manager as Art4JsonApiClientManager; use Swis\JsonApi\Client\Collection; use Swis\JsonApi\Client\CollectionDocument; use Swis\JsonApi\Client\Document; -use Swis\JsonApi\Client\ErrorCollection; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Interfaces\DocumentInterface; use Swis\JsonApi\Client\Interfaces\DocumentParserInterface; use Swis\JsonApi\Client\Interfaces\ItemInterface; use Swis\JsonApi\Client\Interfaces\ManyRelationInterface; use Swis\JsonApi\Client\Interfaces\OneRelationInterface; use Swis\JsonApi\Client\ItemDocument; -use Swis\JsonApi\Client\Jsonapi; -use Swis\JsonApi\Client\Links; -use Swis\JsonApi\Client\Meta; class DocumentParser implements DocumentParserInterface { - /** - * @var \Art4\JsonApiClient\Utils\Manager - */ - private $manager; - /** * @var \Swis\JsonApi\Client\Parsers\ItemParser */ @@ -38,9 +26,9 @@ class DocumentParser implements DocumentParserInterface private $collectionParser; /** - * @var \Swis\JsonApi\Client\Parsers\ErrorsParser + * @var \Swis\JsonApi\Client\Parsers\ErrorCollectionParser */ - private $errorsParser; + private $errorCollectionParser; /** * @var \Swis\JsonApi\Client\Parsers\LinksParser @@ -58,27 +46,24 @@ class DocumentParser implements DocumentParserInterface private $metaParser; /** - * @param \Art4\JsonApiClient\Utils\Manager $manager - * @param \Swis\JsonApi\Client\Parsers\ItemParser $itemParser - * @param \Swis\JsonApi\Client\Parsers\CollectionParser $collectionParser - * @param \Swis\JsonApi\Client\Parsers\ErrorsParser $errorsParser - * @param \Swis\JsonApi\Client\Parsers\LinksParser $linksParser - * @param \Swis\JsonApi\Client\Parsers\JsonapiParser $jsonapiParser - * @param \Swis\JsonApi\Client\Parsers\MetaParser $metaParser + * @param \Swis\JsonApi\Client\Parsers\ItemParser $itemParser + * @param \Swis\JsonApi\Client\Parsers\CollectionParser $collectionParser + * @param \Swis\JsonApi\Client\Parsers\ErrorCollectionParser $errorCollectionParser + * @param \Swis\JsonApi\Client\Parsers\LinksParser $linksParser + * @param \Swis\JsonApi\Client\Parsers\JsonapiParser $jsonapiParser + * @param \Swis\JsonApi\Client\Parsers\MetaParser $metaParser */ public function __construct( - Art4JsonApiClientManager $manager, ItemParser $itemParser, CollectionParser $collectionParser, - ErrorsParser $errorsParser, + ErrorCollectionParser $errorCollectionParser, LinksParser $linksParser, JsonapiParser $jsonapiParser, MetaParser $metaParser ) { - $this->manager = $manager; $this->itemParser = $itemParser; $this->collectionParser = $collectionParser; - $this->errorsParser = $errorsParser; + $this->errorCollectionParser = $errorCollectionParser; $this->linksParser = $linksParser; $this->jsonapiParser = $jsonapiParser; $this->metaParser = $metaParser; @@ -91,41 +76,82 @@ public function __construct( */ public function parse(string $json): DocumentInterface { - /** @var \Art4\JsonApiClient\DocumentInterface $jsonApiDocument */ - $jsonApiDocument = $this->manager->parse($json); - - return $this->getDocument($jsonApiDocument) - ->setLinks($this->parseLinks($jsonApiDocument)) - ->setErrors($this->parseErrors($jsonApiDocument)) - ->setMeta($this->parseMeta($jsonApiDocument)) - ->setJsonapi($this->parseJsonapi($jsonApiDocument)); + $data = $this->decodeJson($json); + + if (!is_object($data)) { + throw new ValidationException(sprintf('Document has to be an object, "%s" given.', gettype($data))); + } + if (!property_exists($data, 'data') && !property_exists($data, 'errors') && !property_exists($data, 'meta')) { + throw new ValidationException('Document MUST contain at least one of the following properties: `data`, `errors`, `meta`.'); + } + if (property_exists($data, 'data') && property_exists($data, 'errors')) { + throw new ValidationException('The properties `data` and `errors` MUST NOT coexist in Document.'); + } + if (!property_exists($data, 'data') && property_exists($data, 'included')) { + throw new ValidationException('If Document does not contain a `data` property, the `included` property MUST NOT be present either.'); + } + if (property_exists($data, 'data') && !is_object($data->data) && !is_array($data->data) && $data->data !== null) { + throw new ValidationException(sprintf('Document property "data" has to be null, an array or an object, "%s" given.', gettype($data))); + } + + $document = $this->getDocument($data); + + if (property_exists($data, 'links')) { + $document->setLinks($this->linksParser->parse($data->links)); + } + + if (property_exists($data, 'errors')) { + $document->setErrors($this->errorCollectionParser->parse($data->errors)); + } + + if (property_exists($data, 'meta')) { + $document->setMeta($this->metaParser->parse($data->meta)); + } + + if (property_exists($data, 'jsonapi')) { + $document->setJsonapi($this->jsonapiParser->parse($data->jsonapi)); + } + + return $document; + } + + /** + * @param string $json + * + * @return mixed + */ + private function decodeJson(string $json) + { + $data = json_decode($json, false); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new ValidationException(sprintf('Unable to parse JSON data: %s', json_last_error_msg()), json_last_error()); + } + + return $data; } /** - * @param \Art4\JsonApiClient\DocumentInterface $jsonApiDocument + * @param mixed $data * * @return \Swis\JsonApi\Client\Interfaces\DocumentInterface */ - private function getDocument(Art4JsonApiDocumentInterface $jsonApiDocument): DocumentInterface + private function getDocument($data): DocumentInterface { - if (!$jsonApiDocument->has('data')) { + if (!property_exists($data, 'data') || $data->data === null) { return new Document(); } - $data = $jsonApiDocument->get('data'); - - if ($data instanceof ResourceItemInterface) { - $document = (new ItemDocument()) - ->setData($this->itemParser->parse($data)); - } elseif ($data instanceof ResourceCollectionInterface) { + if (is_array($data->data)) { $document = (new CollectionDocument()) - ->setData($this->collectionParser->parse($data)); + ->setData($this->collectionParser->parse($data->data)); } else { - throw new \DomainException('Document data is not a Collection or an Item'); + $document = (new ItemDocument()) + ->setData($this->itemParser->parse($data->data)); } - if ($jsonApiDocument->has('included')) { - $document->setIncluded($this->collectionParser->parse($jsonApiDocument->get('included'))); + if (property_exists($data, 'included')) { + $document->setIncluded($this->collectionParser->parse($data->included)); } $allItems = Collection::wrap($document->getData()) @@ -196,60 +222,4 @@ private function getItemKey(ItemInterface $item): string { return sprintf('%s:%s', $item->getType(), $item->getId()); } - - /** - * @param \Art4\JsonApiClient\DocumentInterface $document - * - * @return \Swis\JsonApi\Client\Links|null - */ - private function parseLinks(Art4JsonApiDocumentInterface $document): ?Links - { - if (!$document->has('links')) { - return null; - } - - return $this->linksParser->parse($document->get('links')->asArray()); - } - - /** - * @param \Art4\JsonApiClient\DocumentInterface $document - * - * @return \Swis\JsonApi\Client\ErrorCollection - */ - private function parseErrors(Art4JsonApiDocumentInterface $document): ErrorCollection - { - if (!$document->has('errors')) { - return new ErrorCollection(); - } - - return $this->errorsParser->parse($document->get('errors')); - } - - /** - * @param \Art4\JsonApiClient\DocumentInterface $document - * - * @return \Swis\JsonApi\Client\Meta|null - */ - private function parseMeta(Art4JsonApiDocumentInterface $document): ?Meta - { - if (!$document->has('meta')) { - return null; - } - - return $this->metaParser->parse($document->get('meta')); - } - - /** - * @param \Art4\JsonApiClient\DocumentInterface $document - * - * @return \Swis\JsonApi\Client\Jsonapi|null - */ - private function parseJsonapi(Art4JsonApiDocumentInterface $document): ?Jsonapi - { - if (!$document->has('jsonapi')) { - return null; - } - - return $this->jsonapiParser->parse($document->get('jsonapi')); - } } diff --git a/src/Parsers/ErrorCollectionParser.php b/src/Parsers/ErrorCollectionParser.php new file mode 100644 index 0000000..e699dbd --- /dev/null +++ b/src/Parsers/ErrorCollectionParser.php @@ -0,0 +1,49 @@ +errorParser = $errorParser; + } + + /** + * @param mixed $data + * + * @return \Swis\JsonApi\Client\ErrorCollection + */ + public function parse($data): ErrorCollection + { + if (!is_array($data)) { + throw new ValidationException(sprintf('ErrorCollection has to be in an array, "%s" given.', gettype($data))); + } + if (count($data) === 0) { + throw new ValidationException('ErrorCollection cannot be empty and MUST have at least one Error object.'); + } + + return new ErrorCollection( + array_map( + function ($error) { + return $this->errorParser->parse($error); + }, + $data + ) + ); + } +} diff --git a/src/Parsers/ErrorParser.php b/src/Parsers/ErrorParser.php new file mode 100644 index 0000000..b7c64d7 --- /dev/null +++ b/src/Parsers/ErrorParser.php @@ -0,0 +1,94 @@ +linksParser = $linksParser; + $this->metaParser = $metaParser; + } + + /** + * @param mixed $data + * + * @return \Swis\JsonApi\Client\Error + */ + public function parse($data): Error + { + if (!is_object($data)) { + throw new ValidationException(sprintf('Error has to be an object, "%s" given.', gettype($data))); + } + if (property_exists($data, 'id') && !is_string($data->id)) { + throw new ValidationException(sprintf('Error property "id" has to be a string, "%s" given.', gettype($data->id))); + } + if (property_exists($data, 'status') && !is_string($data->status)) { + throw new ValidationException(sprintf('Error property "status" has to be a string, "%s" given.', gettype($data->status))); + } + if (property_exists($data, 'code') && !is_string($data->code)) { + throw new ValidationException(sprintf('Error property "code" has to be a string, "%s" given.', gettype($data->code))); + } + if (property_exists($data, 'title') && !is_string($data->title)) { + throw new ValidationException(sprintf('Error property "title" has to be a string, "%s" given.', gettype($data->title))); + } + if (property_exists($data, 'detail') && !is_string($data->detail)) { + throw new ValidationException(sprintf('Error property "detail" has to be a string, "%s" given.', gettype($data->detail))); + } + + return new Error( + property_exists($data, 'id') ? $data->id : null, + property_exists($data, 'links') ? $this->linksParser->parse($data->links) : null, + property_exists($data, 'status') ? $data->status : null, + property_exists($data, 'code') ? $data->code : null, + property_exists($data, 'title') ? $data->title : null, + property_exists($data, 'detail') ? $data->detail : null, + property_exists($data, 'source') ? $this->buildErrorSource($data->source) : null, + property_exists($data, 'meta') ? $this->metaParser->parse($data->meta) : null + ); + } + + /** + * @param mixed $data + * + * @return \Swis\JsonApi\Client\ErrorSource + */ + private function buildErrorSource($data): ErrorSource + { + if (!is_object($data)) { + throw new ValidationException(sprintf('ErrorSource has to be an object, "%s" given.', gettype($data))); + } + if (property_exists($data, 'pointer') && !is_string($data->pointer)) { + throw new ValidationException(sprintf('ErrorSource property "pointer" has to be a string, "%s" given.', gettype($data->pointer))); + } + if (property_exists($data, 'parameter') && !is_string($data->parameter)) { + throw new ValidationException(sprintf('ErrorSource property "parameter" has to be a string, "%s" given.', gettype($data->parameter))); + } + + return new ErrorSource( + property_exists($data, 'pointer') ? $data->pointer : null, + property_exists($data, 'parameter') ? $data->parameter : null + ); + } +} diff --git a/src/Parsers/ErrorsParser.php b/src/Parsers/ErrorsParser.php deleted file mode 100644 index 1ea5918..0000000 --- a/src/Parsers/ErrorsParser.php +++ /dev/null @@ -1,108 +0,0 @@ -linksParser = $linksParser; - $this->metaParser = $metaParser; - } - - /** - * @param \Art4\JsonApiClient\ErrorCollection $errorCollection - * - * @return \Swis\JsonApi\Client\ErrorCollection - */ - public function parse(JsonApiErrorCollection $errorCollection): ErrorCollection - { - $errors = new ErrorCollection(); - - foreach ($errorCollection->asArray() as $error) { - $errors->push($this->buildError($error)); - } - - return $errors; - } - - /** - * @param \Art4\JsonApiClient\Error $error - * - * @return \Swis\JsonApi\Client\Error - */ - private function buildError(JsonApiError $error): Error - { - return new Error( - $error->has('id') ? $error->get('id') : null, - $error->has('links') ? $this->buildLinks($error->get('links')) : null, - $error->has('status') ? $error->get('status') : null, - $error->has('code') ? $error->get('code') : null, - $error->has('title') ? $error->get('title') : null, - $error->has('detail') ? $error->get('detail') : null, - $error->has('source') ? $this->buildErrorSource($error->get('source')) : null, - $error->has('meta') ? $this->buildMeta($error->get('meta')) : null - ); - } - - /** - * @param \Art4\JsonApiClient\ErrorLink $errorLink - * - * @return \Swis\JsonApi\Client\Links - */ - private function buildLinks(JsonApiErrorLink $errorLink): Links - { - return $this->linksParser->parse($errorLink->asArray()); - } - - /** - * @param \Art4\JsonApiClient\ErrorSource $errorSource - * - * @return \Swis\JsonApi\Client\ErrorSource - */ - private function buildErrorSource(JsonApiErrorSource $errorSource): ErrorSource - { - return new ErrorSource( - $errorSource->has('pointer') ? $errorSource->get('pointer') : null, - $errorSource->has('parameter') ? $errorSource->get('parameter') : null - ); - } - - /** - * @param \Art4\JsonApiClient\Meta $meta - * - * @return \Swis\JsonApi\Client\Meta - */ - private function buildMeta(JsonApiMeta $meta): Meta - { - return $this->metaParser->parse($meta); - } -} diff --git a/src/Parsers/ItemParser.php b/src/Parsers/ItemParser.php index 3758f9f..2dec63c 100644 --- a/src/Parsers/ItemParser.php +++ b/src/Parsers/ItemParser.php @@ -2,11 +2,8 @@ namespace Swis\JsonApi\Client\Parsers; -use Art4\JsonApiClient\RelationshipCollectionInterface; -use Art4\JsonApiClient\ResourceIdentifierCollectionInterface; -use Art4\JsonApiClient\ResourceIdentifierInterface; -use Art4\JsonApiClient\ResourceItemInterface; use Swis\JsonApi\Client\Collection; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Interfaces\DataInterface; use Swis\JsonApi\Client\Interfaces\ItemInterface; use Swis\JsonApi\Client\Interfaces\TypeMapperInterface; @@ -45,32 +42,56 @@ public function __construct(TypeMapperInterface $typeMapper, LinksParser $linksP } /** - * @param \Art4\JsonApiClient\ResourceItemInterface $jsonApiItem + * @param mixed $data * * @return \Swis\JsonApi\Client\Interfaces\ItemInterface */ - public function parse(ResourceItemInterface $jsonApiItem): ItemInterface + public function parse($data): ItemInterface { - $item = $this->getItemInstance($jsonApiItem->get('type')); + if (!is_object($data)) { + throw new ValidationException(sprintf('Resource has to be an object, "%s" given.', gettype($data))); + } + if (!property_exists($data, 'type')) { + throw new ValidationException('Resource object MUST contain a type.'); + } + if (!property_exists($data, 'id')) { + throw new ValidationException('Resource object MUST contain an id.'); + } + if (!is_string($data->type)) { + throw new ValidationException(sprintf('Resource property "type" has to be a string, "%s" given.', gettype($data->type))); + } + if (!is_string($data->id) && !is_numeric($data->id)) { + throw new ValidationException(sprintf('Resource property "id" has to be a string, "%s" given.', gettype($data->id))); + } + if (property_exists($data, 'attributes')) { + if (!is_object($data->attributes)) { + throw new ValidationException(sprintf('Resource property "attributes" has to be an object, "%s" given.', gettype($data->attributes))); + } + if (property_exists($data->attributes, 'type') || property_exists($data->attributes, 'id') || property_exists($data->attributes, 'relationships') || property_exists($data->attributes, 'links')) { + throw new ValidationException('These properties are not allowed in attributes: `type`, `id`, `relationships`, `links`'); + } + } + + $item = $this->getItemInstance($data->type); - if ($jsonApiItem->has('id')) { - $item->setId($jsonApiItem->get('id')); + if (property_exists($data, 'id')) { + $item->setId($data->id); } - if ($jsonApiItem->has('attributes')) { - $item->fill($jsonApiItem->get('attributes')->asArray(true)); + if (property_exists($data, 'attributes')) { + $item->fill((array)$data->attributes); } - if ($jsonApiItem->has('relationships')) { - $this->setRelations($item, $jsonApiItem->get('relationships')); + if (property_exists($data, 'relationships')) { + $this->setRelations($item, $data->relationships); } - if ($jsonApiItem->has('links')) { - $item->setLinks($this->linksParser->parse($jsonApiItem->get('links')->asArray())); + if (property_exists($data, 'links')) { + $item->setLinks($this->linksParser->parse($data->links)); } - if ($jsonApiItem->has('meta')) { - $item->setMeta($this->metaParser->parse($jsonApiItem->get('meta'))); + if (property_exists($data, 'meta')) { + $item->setMeta($this->metaParser->parse($data->meta)); } return $item; @@ -91,34 +112,50 @@ private function getItemInstance(string $type): ItemInterface } /** - * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item - * @param \Art4\JsonApiClient\RelationshipCollectionInterface $relationships + * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item + * @param mixed $data */ - private function setRelations(ItemInterface $item, RelationshipCollectionInterface $relationships): void + private function setRelations(ItemInterface $item, $data): void { - /** @var \Art4\JsonApiClient\RelationshipInterface $relationship */ - foreach ($relationships->asArray() as $name => $relationship) { - $data = new Collection(); - if ($relationship->has('data')) { - $data = $this->parseRelationshipData($relationship->get('data')); + if (!is_object($data)) { + throw new ValidationException(sprintf('Relationships has to be an object, "%s" given.', gettype($data))); + } + if (property_exists($data, 'type') || property_exists($data, 'id')) { + throw new ValidationException('These properties are not allowed in relationships: `type`, `id`.'); + } + + foreach ($data as $name => $relationship) { + if ($item->hasAttribute($name)) { + throw new ValidationException(sprintf('Relationship "%s" cannot be set because it already exists in Resource object.', $name)); + } + if (!is_object($relationship)) { + throw new ValidationException(sprintf('Relationship has to be an object, "%s" given.', gettype($relationship))); + } + if (!property_exists($relationship, 'links') && !property_exists($relationship, 'data') && !property_exists($relationship, 'meta')) { + throw new ValidationException('Relationship object MUST contain at least one of the following properties: `links`, `data`, `meta`.'); + } + + $value = new Collection(); + if (property_exists($relationship, 'data') && $relationship->data !== null) { + $value = $this->parseRelationshipData($relationship->data); } $links = null; - if ($relationship->has('links')) { - $links = $this->linksParser->parse($relationship->get('links')->asArray()); + if (property_exists($relationship, 'links')) { + $links = $this->linksParser->parse($relationship->links); } $meta = null; - if ($relationship->has('meta')) { - $meta = $this->metaParser->parse($relationship->get('meta')); + if (property_exists($relationship, 'meta')) { + $meta = $this->metaParser->parse($relationship->meta); } - $item->setRelation($name, $data, $links, $meta); + $item->setRelation($name, $value, $links, $meta); } } /** - * @param \Art4\JsonApiClient\ResourceIdentifierInterface|\Art4\JsonApiClient\ResourceIdentifierCollectionInterface $data + * @param mixed $data * * @throws \InvalidArgumentException * @@ -126,21 +163,31 @@ private function setRelations(ItemInterface $item, RelationshipCollectionInterfa */ private function parseRelationshipData($data): DataInterface { - if ($data instanceof ResourceIdentifierInterface) { - return $this->getItemInstance($data->get('type')) - ->setId($data->get('id')); - } - - if ($data instanceof ResourceIdentifierCollectionInterface) { - return Collection::make($data->asArray()) + if (is_array($data)) { + return Collection::make($data) ->map( - function (ResourceIdentifierInterface $identifier) { - return $this->getItemInstance($identifier->get('type')) - ->setId($identifier->get('id')); + function ($identifier) { + return $this->parseRelationshipData($identifier); } ); } - throw new \InvalidArgumentException(sprintf('Expected either %s or %s', ResourceIdentifierInterface::class, ResourceIdentifierCollectionInterface::class)); + if (!is_object($data)) { + throw new ValidationException(sprintf('ResourceIdentifier has to be an object, "%s" given.', gettype($data))); + } + if (!property_exists($data, 'type')) { + throw new ValidationException('ResourceIdentifier object MUST contain a type.'); + } + if (!property_exists($data, 'id')) { + throw new ValidationException('ResourceIdentifier object MUST contain an id.'); + } + if (!is_string($data->type)) { + throw new ValidationException(sprintf('ResourceIdentifier property "type" has to be a string, "%s" given.', gettype($data->type))); + } + if (!is_string($data->id) && !is_numeric($data->id)) { + throw new ValidationException(sprintf('ResourceIdentifier property "id" has to be a string, "%s" given.', gettype($data->type))); + } + + return $this->getItemInstance($data->type)->setId($data->id); } } diff --git a/src/Parsers/JsonapiParser.php b/src/Parsers/JsonapiParser.php index b29450d..ad9e4b8 100644 --- a/src/Parsers/JsonapiParser.php +++ b/src/Parsers/JsonapiParser.php @@ -2,10 +2,8 @@ namespace Swis\JsonApi\Client\Parsers; -use Art4\JsonApiClient\Jsonapi as JsonApiJsonapi; -use Art4\JsonApiClient\Meta as JsonApiMeta; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Jsonapi; -use Swis\JsonApi\Client\Meta; /** * @internal @@ -26,25 +24,22 @@ public function __construct(MetaParser $metaParser) } /** - * @param \Art4\JsonApiClient\Jsonapi $jsonApi + * @param mixed $data * * @return \Swis\JsonApi\Client\Jsonapi */ - public function parse(JsonApiJsonapi $jsonApi): Jsonapi + public function parse($data): Jsonapi { + if (!is_object($data)) { + throw new ValidationException(sprintf('Jsonapi has to be an object, "%s" given.', gettype($data))); + } + if (property_exists($data, 'version') && !is_string($data->version)) { + throw new ValidationException(sprintf('Jsonapi property "version" has to be a string, "%s" given.', gettype($data->version))); + } + return new Jsonapi( - $jsonApi->has('version') ? $jsonApi->get('version') : null, - $jsonApi->has('meta') ? $this->buildMeta($jsonApi->get('meta')) : null + property_exists($data, 'version') ? $data->version : null, + property_exists($data, 'meta') ? $this->metaParser->parse($data->meta) : null ); } - - /** - * @param \Art4\JsonApiClient\Meta $meta - * - * @return \Swis\JsonApi\Client\Meta - */ - private function buildMeta(JsonApiMeta $meta): Meta - { - return $this->metaParser->parse($meta); - } } diff --git a/src/Parsers/LinksParser.php b/src/Parsers/LinksParser.php index 8365873..0677e50 100644 --- a/src/Parsers/LinksParser.php +++ b/src/Parsers/LinksParser.php @@ -2,10 +2,9 @@ namespace Swis\JsonApi\Client\Parsers; -use Art4\JsonApiClient\Meta as JsonApiMeta; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Link; use Swis\JsonApi\Client\Links; -use Swis\JsonApi\Client\Meta; /** * @internal @@ -26,43 +25,44 @@ public function __construct(MetaParser $metaParser) } /** - * @param array $links + * @param mixed $data * * @return \Swis\JsonApi\Client\Links */ - public function parse(array $links): Links + public function parse($data): Links { return new Links( array_map( function ($link) { return $this->buildLink($link); }, - $links + (array)$data ) ); } /** - * @param \Art4\JsonApiClient\DocumentLink|\Art4\JsonApiClient\ErrorLink|\Art4\JsonApiClient\Link|\Art4\JsonApiClient\RelationshipLink|\Art4\JsonApiClient\ResourceItemLink|string $link + * @param mixed $data * * @return \Swis\JsonApi\Client\Link */ - private function buildLink($link): Link + private function buildLink($data): ? Link { - if (is_string($link)) { - return new Link($link); + if ($data === null) { + return null; } - return new Link($link->get('href'), $link->has('meta') ? $this->buildMeta($link->get('meta')) : null); - } + if (is_string($data)) { + return new Link($data); + } - /** - * @param \Art4\JsonApiClient\Meta $meta - * - * @return \Swis\JsonApi\Client\Meta - */ - private function buildMeta(JsonApiMeta $meta): Meta - { - return $this->metaParser->parse($meta); + if (!is_object($data)) { + throw new ValidationException(sprintf('Link has to be an object, string or null, "%s" given.', gettype($data))); + } + if (!property_exists($data, 'href')) { + throw new ValidationException('Link must have a "href" attribute.'); + } + + return new Link($data->href, property_exists($data, 'meta') ? $this->metaParser->parse($data->meta) : null); } } diff --git a/src/Parsers/MetaParser.php b/src/Parsers/MetaParser.php index 072dce3..0efceb3 100644 --- a/src/Parsers/MetaParser.php +++ b/src/Parsers/MetaParser.php @@ -2,7 +2,7 @@ namespace Swis\JsonApi\Client\Parsers; -use Art4\JsonApiClient\Meta as JsonApiMeta; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Meta; /** @@ -11,12 +11,16 @@ class MetaParser { /** - * @param \Art4\JsonApiClient\Meta $meta + * @param mixed $data * * @return \Swis\JsonApi\Client\Meta */ - public function parse(JsonApiMeta $meta): Meta + public function parse($data): Meta { - return new Meta($meta->asArray(true)); + if (!is_object($data)) { + throw new ValidationException(sprintf('Meta has to be an object, "%s" given.', gettype($data))); + } + + return new Meta((array)$data); } } diff --git a/tests/Parsers/CollectionParserTest.php b/tests/Parsers/CollectionParserTest.php index ca6f2ef..9662d9b 100644 --- a/tests/Parsers/CollectionParserTest.php +++ b/tests/Parsers/CollectionParserTest.php @@ -2,8 +2,8 @@ namespace Swis\JsonApi\Client\Tests\Parsers; -use Art4\JsonApiClient\Utils\Manager; use Swis\JsonApi\Client\Collection; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Parsers\CollectionParser; use Swis\JsonApi\Client\Parsers\ItemParser; use Swis\JsonApi\Client\Tests\AbstractTest; @@ -14,7 +14,7 @@ class CollectionParserTest extends AbstractTest /** * @test */ - public function it_converts_art4resourcecollection_to_collection() + public function it_converts_data_to_collection() { $itemParser = $this->createMock(ItemParser::class); $itemParser->expects($this->exactly(2)) @@ -22,7 +22,7 @@ public function it_converts_art4resourcecollection_to_collection() ->willReturn(new PlainItem()); $parser = new CollectionParser($itemParser); - $collection = $parser->parse($this->getArt4ResourceCollection()); + $collection = $parser->parse($this->getResourceCollection()); $this->assertInstanceOf(Collection::class, $collection); $this->assertEquals(2, $collection->count()); @@ -32,32 +32,57 @@ public function it_converts_art4resourcecollection_to_collection() } /** - * @return \Art4\JsonApiClient\ResourceCollection + * @test + * @dataProvider provideInvalidData + * + * @param mixed $invalidData */ - protected function getArt4ResourceCollection() + public function it_throws_when_data_is_not_an_array($invalidData) + { + $parser = new CollectionParser($this->createMock(ItemParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidData); + } + + public function provideInvalidData(): array { - $collection = [ - 'data' => [ - [ - 'id' => '1', - 'type' => 'plain', - 'attributes' => [ - 'foo' => 'bar', - ], + $object = new \stdClass(); + $object->foo = 'bar'; + + return [ + [1], + [1.5], + [false], + [null], + ['foo'], + [$object], + ]; + } + + /** + * @return \stdClass + */ + protected function getResourceCollection() + { + $data = [ + [ + 'id' => '1', + 'type' => 'plain', + 'attributes' => [ + 'foo' => 'bar', ], - [ - 'id' => '2', - 'type' => 'plain', - 'attributes' => [ - 'foo' => 'bar', - ], + ], + [ + 'id' => '2', + 'type' => 'plain', + 'attributes' => [ + 'foo' => 'bar', ], ], ]; - $manager = new Manager(); - $jsonApiItem = $manager->parse(json_encode($collection)); - - return $jsonApiItem->get('data'); + return json_decode(json_encode($data), false); } } diff --git a/tests/Parsers/DocumentParserTest.php b/tests/Parsers/DocumentParserTest.php index 0aaae69..71d1c79 100644 --- a/tests/Parsers/DocumentParserTest.php +++ b/tests/Parsers/DocumentParserTest.php @@ -2,13 +2,12 @@ namespace Swis\JsonApi\Client\Tests\Parsers; -use Art4\JsonApiClient\Exception\ValidationException; -use Art4\JsonApiClient\Utils\Manager; use Swis\JsonApi\Client\Collection; use Swis\JsonApi\Client\CollectionDocument; use Swis\JsonApi\Client\Document; use Swis\JsonApi\Client\Error; use Swis\JsonApi\Client\ErrorCollection; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Interfaces\ItemInterface; use Swis\JsonApi\Client\ItemDocument; use Swis\JsonApi\Client\Jsonapi; @@ -17,7 +16,8 @@ use Swis\JsonApi\Client\Meta; use Swis\JsonApi\Client\Parsers\CollectionParser; use Swis\JsonApi\Client\Parsers\DocumentParser; -use Swis\JsonApi\Client\Parsers\ErrorsParser; +use Swis\JsonApi\Client\Parsers\ErrorCollectionParser; +use Swis\JsonApi\Client\Parsers\ErrorParser; use Swis\JsonApi\Client\Parsers\ItemParser; use Swis\JsonApi\Client\Parsers\JsonapiParser; use Swis\JsonApi\Client\Parsers\LinksParser; @@ -66,9 +66,73 @@ public function provideInvalidJson(): array return [ [''], ['Foo bar'], + [json_encode(null)], + [json_encode(1)], + [json_encode(1.5)], + [json_encode(false)], [json_encode('Foo bar')], [json_encode(['Foo bar'])], - [json_encode(['foo' => 'bar'])], + ]; + } + + /** + * @test + */ + public function it_throws_when_data_errors_and_meta_are_missing() + { + $parser = $this->getDocumentParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_encode(new \stdClass())); + } + + /** + * @test + */ + public function it_throws_when_both_data_and_errors_are_present() + { + $parser = $this->getDocumentParser(); + + $this->expectException(ValidationException::class); + + $parser->parse('{"data": [], "errors": []}'); + } + + /** + * @test + */ + public function it_throws_when_included_is_present_but_data_is_not() + { + $parser = $this->getDocumentParser(); + + $this->expectException(ValidationException::class); + + $parser->parse('{"included": [], "errors": []}'); + } + + /** + * @test + * @dataProvider provideInvalidData + * + * @param mixed $invalidData + */ + public function it_throws_when_data_is_not_an_array_object_or_null($invalidData) + { + $parser = $this->getDocumentParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidData); + } + + public function provideInvalidData(): array + { + return [ + ['{"data": 1}'], + ['{"data": 1.5}'], + ['{"data": false}'], + ['{"data": "foo"}'], ]; } @@ -384,10 +448,9 @@ private function getDocumentParser(TypeMapper $typeMapper = null): DocumentParse $itemParser = new ItemParser($typeMapper ?? new TypeMapper(), $linksParser, $metaParser); return new DocumentParser( - new Manager(), $itemParser, new CollectionParser($itemParser), - new ErrorsParser($linksParser, $metaParser), + new ErrorCollectionParser(new ErrorParser($linksParser, $metaParser)), $linksParser, new JsonapiParser($metaParser), $metaParser diff --git a/tests/Parsers/ErrorCollectionParserTest.php b/tests/Parsers/ErrorCollectionParserTest.php new file mode 100644 index 0000000..ec8aa97 --- /dev/null +++ b/tests/Parsers/ErrorCollectionParserTest.php @@ -0,0 +1,92 @@ +createMock(ErrorParser::class); + $errorParser->expects($this->exactly(2)) + ->method('parse') + ->willReturn(new Error()); + + $parser = new ErrorCollectionParser($errorParser); + $errorCollection = $parser->parse($this->getErrorCollection()); + + $this->assertInstanceOf(ErrorCollection::class, $errorCollection); + $this->assertEquals(2, $errorCollection->count()); + + $this->assertInstanceOf(Error::class, $errorCollection->get(0)); + $this->assertInstanceOf(Error::class, $errorCollection->get(1)); + } + + /** + * @test + * @dataProvider provideInvalidData + * + * @param mixed $invalidData + */ + public function it_throws_when_data_is_not_an_array($invalidData) + { + $parser = new ErrorCollectionParser($this->createMock(ErrorParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidData); + } + + public function provideInvalidData(): array + { + $object = new \stdClass(); + $object->foo = 'bar'; + + return [ + [1], + [1.5], + [false], + [null], + ['foo'], + [$object], + ]; + } + + /** + * @test + */ + public function it_throws_when_data_is_empty() + { + $parser = new ErrorCollectionParser($this->createMock(ErrorParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse([]); + } + + /** + * @return \stdClass + */ + protected function getErrorCollection() + { + $data = [ + [ + 'code' => 'json_client_content_id_in_object_not_equal_to_id_parameter', + ], + [ + 'code' => 'json_client_content_id_in_object_not_equal_to_id_parameter', + ], + ]; + + return json_decode(json_encode($data), false); + } +} diff --git a/tests/Parsers/ErrorParserTest.php b/tests/Parsers/ErrorParserTest.php new file mode 100644 index 0000000..4d9770c --- /dev/null +++ b/tests/Parsers/ErrorParserTest.php @@ -0,0 +1,314 @@ +parse($this->getError()); + + $this->assertInstanceOf(Error::class, $error); + $this->assertInstanceOf(Links::class, $error->getLinks()); + $this->assertInstanceOf(Meta::class, $error->getMeta()); + $this->assertInstanceOf(ErrorSource::class, $error->getSource()); + + $this->assertEquals('1', $error->getId()); + $this->assertEquals('http://example.com/docs/error/json_client_content_id_in_object_not_equal_to_id_parameter', $error->getLinks()->about->getHref()); + $this->assertEquals('400', $error->getStatus()); + $this->assertEquals('json_client_content_id_in_object_not_equal_to_id_parameter', $error->getCode()); + $this->assertEquals('I refuse to save a sport with this id. ✟', $error->getTitle()); + $this->assertEquals("id is '666', id is '666'", $error->getDetail()); + $this->assertEquals('', $error->getSource()->getPointer()); + $this->assertEquals('666', $error->getSource()->getParameter()); + $this->assertEquals('Copyright 2015 Example Corp.', $error->getMeta()->copyright); + } + + /** + * @test + * @dataProvider provideInvalidData + * + * @param mixed $invalidData + */ + public function it_throws_when_data_is_not_an_object($invalidData) + { + $parser = new ErrorParser($this->createMock(LinksParser::class), $this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidData); + } + + public function provideInvalidData(): array + { + return [ + [1], + [1.5], + [false], + [null], + ['foo'], + [[]], + ]; + } + + /** + * @test + * @dataProvider provideInvalidIdError + * + * @param mixed $invalidError + */ + public function it_throws_when_id_is_not_a_string($invalidError) + { + $parser = new ErrorParser($this->createMock(LinksParser::class), $this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidError); + } + + public function provideInvalidIdError(): array + { + return [ + [json_decode('{"id": 1}', false)], + [json_decode('{"id": 1.5}', false)], + [json_decode('{"id": false}', false)], + [json_decode('{"id": null}', false)], + [json_decode('{"id": []}', false)], + [json_decode('{"id": {}}', false)], + ]; + } + + /** + * @test + * @dataProvider provideInvalidStatusError + * + * @param mixed $invalidError + */ + public function it_throws_when_status_is_not_a_string($invalidError) + { + $parser = new ErrorParser($this->createMock(LinksParser::class), $this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidError); + } + + public function provideInvalidStatusError(): array + { + return [ + [json_decode('{"status": 1}', false)], + [json_decode('{"status": 1.5}', false)], + [json_decode('{"status": false}', false)], + [json_decode('{"status": null}', false)], + [json_decode('{"status": []}', false)], + [json_decode('{"status": {}}', false)], + ]; + } + + /** + * @test + * @dataProvider provideInvalidCodeError + * + * @param mixed $invalidError + */ + public function it_throws_when_code_is_not_a_string($invalidError) + { + $parser = new ErrorParser($this->createMock(LinksParser::class), $this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidError); + } + + public function provideInvalidCodeError(): array + { + return [ + [json_decode('{"code": 1}', false)], + [json_decode('{"code": 1.5}', false)], + [json_decode('{"code": false}', false)], + [json_decode('{"code": null}', false)], + [json_decode('{"code": []}', false)], + [json_decode('{"code": {}}', false)], + ]; + } + + /** + * @test + * @dataProvider provideInvalidTitleError + * + * @param mixed $invalidError + */ + public function it_throws_when_title_is_not_a_string($invalidError) + { + $parser = new ErrorParser($this->createMock(LinksParser::class), $this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidError); + } + + public function provideInvalidTitleError(): array + { + return [ + [json_decode('{"title": 1}', false)], + [json_decode('{"title": 1.5}', false)], + [json_decode('{"title": false}', false)], + [json_decode('{"title": null}', false)], + [json_decode('{"title": []}', false)], + [json_decode('{"title": {}}', false)], + ]; + } + + /** + * @test + * @dataProvider provideInvalidDetailError + * + * @param mixed $invalidError + */ + public function it_throws_when_detail_is_not_a_string($invalidError) + { + $parser = new ErrorParser($this->createMock(LinksParser::class), $this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidError); + } + + public function provideInvalidDetailError(): array + { + return [ + [json_decode('{"detail": 1}', false)], + [json_decode('{"detail": 1.5}', false)], + [json_decode('{"detail": false}', false)], + [json_decode('{"detail": null}', false)], + [json_decode('{"detail": []}', false)], + [json_decode('{"detail": {}}', false)], + ]; + } + + /** + * @test + * @dataProvider provideInvalidErrorSourceError + * + * @param mixed $invalidError + */ + public function it_throws_when_errorsource_is_not_an_object($invalidError) + { + $parser = new ErrorParser($this->createMock(LinksParser::class), $this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidError); + } + + public function provideInvalidErrorSourceError(): array + { + return [ + [json_decode('{"source": 1}', false)], + [json_decode('{"source": 1.5}', false)], + [json_decode('{"source": false}', false)], + [json_decode('{"source": null}', false)], + [json_decode('{"source": []}', false)], + [json_decode('{"source": "foo"}', false)], + ]; + } + + /** + * @test + * @dataProvider provideInvalidErrorSourcePointerError + * + * @param mixed $invalidError + */ + public function it_throws_when_errorsource_pointer_is_not_an_string($invalidError) + { + $parser = new ErrorParser($this->createMock(LinksParser::class), $this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidError); + } + + public function provideInvalidErrorSourcePointerError(): array + { + return [ + [json_decode('{"source": {"pointer": 1}}', false)], + [json_decode('{"source": {"pointer": 1.5}}', false)], + [json_decode('{"source": {"pointer": false}}', false)], + [json_decode('{"source": {"pointer": null}}', false)], + [json_decode('{"source": {"pointer": []}}', false)], + [json_decode('{"source": {"pointer": {}}}', false)], + ]; + } + + /** + * @test + * @dataProvider provideInvalidErrorSourceParameterError + * + * @param mixed $invalidError + */ + public function it_throws_when_errorsource_parameter_is_not_an_string($invalidError) + { + $parser = new ErrorParser($this->createMock(LinksParser::class), $this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidError); + } + + public function provideInvalidErrorSourceParameterError(): array + { + return [ + [json_decode('{"source": {"parameter": 1}}', false)], + [json_decode('{"source": {"parameter": 1.5}}', false)], + [json_decode('{"source": {"parameter": false}}', false)], + [json_decode('{"source": {"parameter": null}}', false)], + [json_decode('{"source": {"parameter": []}}', false)], + [json_decode('{"source": {"parameter": {}}}', false)], + ]; + } + + /** + * @return \stdClass + */ + protected function getError() + { + $data = [ + 'id' => '1', + 'links' => [ + 'about' => [ + 'href' => 'http://example.com/docs/error/json_client_content_id_in_object_not_equal_to_id_parameter', + 'meta' => [ + 'foo' => 'bar', + ], + ], + ], + 'status' => '400', + 'code' => 'json_client_content_id_in_object_not_equal_to_id_parameter', + 'title' => 'I refuse to save a sport with this id. ✟', + 'detail' => "id is '666', id is '666'", + 'source' => [ + 'pointer' => '', + 'parameter' => '666', + ], + 'meta' => [ + 'copyright' => 'Copyright 2015 Example Corp.', + ], + ]; + + return json_decode(json_encode($data), false); + } +} diff --git a/tests/Parsers/ErrorsParserTest.php b/tests/Parsers/ErrorsParserTest.php deleted file mode 100644 index 165fe1d..0000000 --- a/tests/Parsers/ErrorsParserTest.php +++ /dev/null @@ -1,105 +0,0 @@ -parse($this->getArt4ErrorCollection()); - - $this->assertInstanceOf(ErrorCollection::class, $errorCollection); - $this->assertEquals(2, $errorCollection->count()); - - $errorCollection->each( - function (Error $error) { - $this->assertInstanceOf(Error::class, $error); - $this->assertInstanceOf(Links::class, $error->getLinks()); - $this->assertInstanceOf(Meta::class, $error->getMeta()); - $this->assertInstanceOf(ErrorSource::class, $error->getSource()); - - $this->assertEquals('http://example.com/docs/error/json_client_content_id_in_object_not_equal_to_id_parameter', $error->getLinks()['about']->getHref()); - $this->assertEquals('400', $error->getStatus()); - $this->assertEquals('json_client_content_id_in_object_not_equal_to_id_parameter', $error->getCode()); - $this->assertEquals('I refuse to save a sport with this id. ✟', $error->getTitle()); - $this->assertEquals("id is '666', id is '666'", $error->getDetail()); - $this->assertEquals('', $error->getSource()->getPointer()); - $this->assertEquals('666', $error->getSource()->getParameter()); - $this->assertEquals('Copyright 2015 Example Corp.', $error->getMeta()->copyright); - } - ); - - $this->assertEquals(1, $errorCollection->first()->getId()); - $this->assertEquals(2, $errorCollection->get(1)->getId()); - } - - /** - * @return \Art4\JsonApiClient\ErrorCollection - */ - protected function getArt4ErrorCollection() - { - $errors = [ - 'errors' => [ - [ - 'id' => '1', - 'links' => [ - 'about' => 'http://example.com/docs/error/json_client_content_id_in_object_not_equal_to_id_parameter', - ], - 'status' => '400', - 'code' => 'json_client_content_id_in_object_not_equal_to_id_parameter', - 'title' => 'I refuse to save a sport with this id. ✟', - 'detail' => "id is '666', id is '666'", - 'source' => [ - 'pointer' => '', - 'parameter' => '666', - ], - 'meta' => [ - 'copyright' => 'Copyright 2015 Example Corp.', - ], - ], - [ - 'id' => '2', - 'links' => [ - 'about' => [ - 'href' => 'http://example.com/docs/error/json_client_content_id_in_object_not_equal_to_id_parameter', - 'meta' => [ - 'foo' => 'bar', - ], - ], - ], - 'status' => '400', - 'code' => 'json_client_content_id_in_object_not_equal_to_id_parameter', - 'title' => 'I refuse to save a sport with this id. ✟', - 'detail' => "id is '666', id is '666'", - 'source' => [ - 'pointer' => '', - 'parameter' => '666', - ], - 'meta' => [ - 'copyright' => 'Copyright 2015 Example Corp.', - ], - ], - ], - ]; - - $manager = new Manager(); - $jsonApiItem = $manager->parse(json_encode($errors)); - - return $jsonApiItem->get('errors'); - } -} diff --git a/tests/Parsers/ItemParserTest.php b/tests/Parsers/ItemParserTest.php index c887ec6..bfdb961 100644 --- a/tests/Parsers/ItemParserTest.php +++ b/tests/Parsers/ItemParserTest.php @@ -2,8 +2,8 @@ namespace Swis\JsonApi\Client\Tests\Parsers; -use Art4\JsonApiClient\Utils\Manager; use Swis\JsonApi\Client\Collection; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Interfaces\ItemInterface; use Swis\JsonApi\Client\Interfaces\TypeMapperInterface; use Swis\JsonApi\Client\Link; @@ -28,7 +28,7 @@ class ItemParserTest extends AbstractTest /** * @test */ - public function it_converts_art4resource_to_item() + public function it_converts_data_to_item() { $parser = $this->getItemParser(); $item = $parser->parse($this->getJsonApiItemMock('master', '1')); @@ -42,6 +42,387 @@ public function it_converts_art4resource_to_item() static::assertInstanceOf(Meta::class, $item->getMeta()); } + /** + * @test + * @dataProvider provideInvalidData + * + * @param mixed $invalidData + */ + public function it_throws_when_data_is_not_an_object($invalidData) + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidData); + } + + public function provideInvalidData(): array + { + return [ + [1], + [1.5], + [false], + [null], + ['foo'], + [[]], + ]; + } + + /** + * @test + */ + public function it_throws_when_item_does_not_have_type_property() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo"}', false)); + } + + /** + * @test + */ + public function it_throws_when_item_does_not_have_id_property() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"type": "foo"}', false)); + } + + /** + * @test + * @dataProvider provideInvalidIdItem + * + * @param mixed $invalidItem + */ + public function it_throws_when_id_is_not_a_string($invalidItem) + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidItem); + } + + public function provideInvalidIdItem(): array + { + return [ + [json_decode('{"type": "foo", "id": false}', false)], + [json_decode('{"type": "foo", "id": null}', false)], + [json_decode('{"type": "foo", "id": []}', false)], + [json_decode('{"type": "foo", "id": {}}', false)], + ]; + } + + /** + * @test + * @dataProvider provideInvalidTypeItem + * + * @param mixed $invalidItem + */ + public function it_throws_when_type_is_not_a_string($invalidItem) + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidItem); + } + + public function provideInvalidTypeItem(): array + { + return [ + [json_decode('{"id": "foo", "type": 1}', false)], + [json_decode('{"id": "foo", "type": 1.5}', false)], + [json_decode('{"id": "foo", "type": false}', false)], + [json_decode('{"id": "foo", "type": null}', false)], + [json_decode('{"id": "foo", "type": []}', false)], + [json_decode('{"id": "foo", "type": {}}', false)], + ]; + } + + /** + * @test + * @dataProvider provideInvalidAttributesItem + * + * @param mixed $invalidItem + */ + public function it_throws_when_attributes_is_not_an_object($invalidItem) + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidItem); + } + + public function provideInvalidAttributesItem(): array + { + return [ + [json_decode('{"id": "foo", "type": "foo", "attributes": 1}', false)], + [json_decode('{"id": "foo", "type": "foo", "attributes": 1.5}', false)], + [json_decode('{"id": "foo", "type": "foo", "attributes": false}', false)], + [json_decode('{"id": "foo", "type": "foo", "attributes": null}', false)], + [json_decode('{"id": "foo", "type": "foo", "attributes": []}', false)], + [json_decode('{"id": "foo", "type": "foo", "attributes": "foo"}', false)], + ]; + } + + /** + * @test + */ + public function it_throws_when_type_is_present_in_attributes() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo", "type": "foo", "attributes": {"type": null}}', false)); + } + + /** + * @test + */ + public function it_throws_when_id_is_present_in_attributes() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo", "type": "foo", "attributes": {"id": null}}', false)); + } + + /** + * @test + */ + public function it_throws_when_relationships_is_present_in_attributes() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo", "type": "foo", "attributes": {"relationships": null}}', false)); + } + + /** + * @test + */ + public function it_throws_when_links_is_present_in_attributes() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo", "type": "foo", "attributes": {"links": null}}', false)); + } + + /** + * @test + * @dataProvider provideInvalidRelationshipsItem + * + * @param mixed $invalidItem + */ + public function it_throws_when_relationships_is_not_an_object($invalidItem) + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidItem); + } + + public function provideInvalidRelationshipsItem(): array + { + return [ + [json_decode('{"id": "foo", "type": "foo", "relationships": 1}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": 1.5}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": false}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": null}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": []}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": "foo"}', false)], + ]; + } + + /** + * @test + */ + public function it_throws_when_type_is_present_in_relationships() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo", "type": "foo", "relationships": {"type": null}}', false)); + } + + /** + * @test + */ + public function it_throws_when_id_is_present_in_relationships() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo", "type": "foo", "relationships": {"id": null}}', false)); + } + + /** + * @test + */ + public function it_throws_when_property_is_present_in_both_attributes_and_relationships() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo", "type": "foo", "attributes": {"foo": "bar"}, "relationships": {"foo": "bar"}}', false)); + } + + /** + * @test + * @dataProvider provideInvalidRelationshipsItemItem + * + * @param mixed $invalidItem + */ + public function it_throws_when_relationships_item_is_not_an_object($invalidItem) + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidItem); + } + + public function provideInvalidRelationshipsItemItem(): array + { + return [ + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": 1}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": 1.5}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": false}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": null}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": []}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": "foo"}}', false)], + ]; + } + + /** + * @test + */ + public function it_throws_when_relationships_item_misses_links_data_and_meta() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {}}}', false)); + } + + /** + * @test + * @dataProvider provideInvalidRelationshipsItemIdentifierItem + * + * @param mixed $invalidItem + */ + public function it_throws_when_relationships_item_identifier_is_not_an_object_array_or_null($invalidItem) + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidItem); + } + + public function provideInvalidRelationshipsItemIdentifierItem(): array + { + return [ + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": 1}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": 1.5}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": false}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": "foo"}}}', false)], + ]; + } + + /** + * @test + */ + public function it_throws_when_relationships_item_identifier_does_not_have_type_property() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"id": "foo"}}}}', false)); + } + + /** + * @test + */ + public function it_throws_when_relationships_item_identifier_does_not_have_id_property() + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"type": "foo"}}}}', false)); + } + + /** + * @test + * @dataProvider provideInvalidRelationshipsItemIdentifierIdItem + * + * @param mixed $invalidItem + */ + public function it_throws_when_relationships_item_identifier_id_is_not_a_string($invalidItem) + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidItem); + } + + public function provideInvalidRelationshipsItemIdentifierIdItem(): array + { + return [ + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"type": "foo", "id": false}}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"type": "foo", "id": null}}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"type": "foo", "id": []}}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"type": "foo", "id": {}}}}}', false)], + ]; + } + + /** + * @test + * @dataProvider provideInvalidRelationshipsItemIdentifierTypeItem + * + * @param mixed $invalidItem + */ + public function it_throws_when_relationships_item_identifier_type_is_not_a_string($invalidItem) + { + $parser = $this->getItemParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidItem); + } + + public function provideInvalidRelationshipsItemIdentifierTypeItem(): array + { + return [ + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"id": "foo", "type": 1}}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"id": "foo", "type": 1.5}}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"id": "foo", "type": false}}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"id": "foo", "type": null}}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"id": "foo", "type": []}}}}', false)], + [json_decode('{"id": "foo", "type": "foo", "relationships": {"foo": {"data": {"id": "foo", "type": {}}}}}', false)], + ]; + } + /** * @test */ @@ -260,149 +641,86 @@ static function (string $type) { private function getJsonApiItemMock($type, $id) { $data = [ - 'data' => [ - 'type' => $type, - 'id' => $id, - 'attributes' => [ - 'description' => 'test', - 'active' => true, - ], - 'relationships' => [ - 'child' => [ - 'data' => [ - 'type' => 'child', - 'id' => '2', - ], - 'links' => [ - 'self' => 'http://example.com/'.$type.'/'.$id.'/relationships/child', - ], - 'meta' => [ - 'foo' => 'bar', - ], + 'type' => $type, + 'id' => $id, + 'attributes' => [ + 'description' => 'test', + 'active' => true, + ], + 'relationships' => [ + 'child' => [ + 'data' => [ + 'type' => 'child', + 'id' => '2', ], - 'children' => [ - 'data' => [ - [ - 'type' => 'child', - 'id' => '3', - ], - [ - 'type' => 'child', - 'id' => '4', - ], - ], - 'links' => [ - 'self' => 'http://example.com/'.$type.'/'.$id.'/relationships/children', - ], - 'meta' => [ - 'foo' => 'bar', - ], + 'links' => [ + 'self' => 'http://example.com/'.$type.'/'.$id.'/relationships/child', ], - 'morph' => [ - 'data' => [ + 'meta' => [ + 'foo' => 'bar', + ], + ], + 'children' => [ + 'data' => [ + [ 'type' => 'child', - 'id' => '5', + 'id' => '3', ], - 'links' => [ - 'self' => 'http://example.com/'.$type.'/'.$id.'/relationships/morph', - ], - 'meta' => [ - 'foo' => 'bar', + [ + 'type' => 'child', + 'id' => '4', ], ], - 'morphmany' => [ - 'data' => [ - [ - 'type' => 'child', - 'id' => '6', - ], - [ - 'type' => 'child', - 'id' => '7', - ], - [ - 'type' => 'child', - 'id' => '8', - ], - ], - 'links' => [ - 'self' => 'http://example.com/'.$type.'/'.$id.'/relationships/morphmany', - ], - 'meta' => [ - 'foo' => 'bar', - ], + 'links' => [ + 'self' => 'http://example.com/'.$type.'/'.$id.'/relationships/children', ], - ], - 'links' => [ - 'self' => 'http://example.com/master/1', - ], - 'meta' => [ - 'foo' => 'bar', - ], - ], - 'included' => [ - [ - 'type' => 'child', - 'id' => '2', - 'attributes' => [ - 'description' => 'test', - 'active' => true, + 'meta' => [ + 'foo' => 'bar', ], ], - [ - 'type' => 'child', - 'id' => '3', - 'attributes' => [ - 'description' => 'test3', - 'active' => true, + 'morph' => [ + 'data' => [ + 'type' => 'child', + 'id' => '5', ], - ], - [ - 'type' => 'child', - 'id' => '4', - 'attributes' => [ - 'description' => 'test4', - 'active' => true, + 'links' => [ + 'self' => 'http://example.com/'.$type.'/'.$id.'/relationships/morph', ], - ], - [ - 'type' => 'child', - 'id' => '5', - 'attributes' => [ - 'description' => 'test5', - 'active' => true, + 'meta' => [ + 'foo' => 'bar', ], ], - [ - 'type' => 'child', - 'id' => '6', - 'attributes' => [ - 'description' => 'test6', - 'active' => true, + 'morphmany' => [ + 'data' => [ + [ + 'type' => 'child', + 'id' => '6', + ], + [ + 'type' => 'child', + 'id' => '7', + ], + [ + 'type' => 'child', + 'id' => '8', + ], ], - ], - [ - 'type' => 'child', - 'id' => '7', - 'attributes' => [ - 'description' => 'test7', - 'active' => true, + 'links' => [ + 'self' => 'http://example.com/'.$type.'/'.$id.'/relationships/morphmany', ], - ], - [ - 'type' => 'child', - 'id' => '8', - 'attributes' => [ - 'description' => 'test8', - 'active' => true, + 'meta' => [ + 'foo' => 'bar', ], ], ], + 'links' => [ + 'self' => 'http://example.com/master/1', + ], + 'meta' => [ + 'foo' => 'bar', + ], ]; - $manager = new Manager(); - $jsonApiItem = $manager->parse(json_encode($data)); - - return $jsonApiItem->get('data'); + return json_decode(json_encode($data), false); } } diff --git a/tests/Parsers/JsonapiParserTest.php b/tests/Parsers/JsonapiParserTest.php index e649657..6f884c7 100644 --- a/tests/Parsers/JsonapiParserTest.php +++ b/tests/Parsers/JsonapiParserTest.php @@ -2,7 +2,7 @@ namespace Swis\JsonApi\Client\Tests\Parsers; -use Art4\JsonApiClient\Utils\Manager; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Jsonapi; use Swis\JsonApi\Client\Meta; use Swis\JsonApi\Client\Parsers\JsonapiParser; @@ -14,10 +14,10 @@ class JsonapiParserTest extends AbstractTest /** * @test */ - public function it_converts_art4jsonapi_to_jsonapi() + public function it_converts_data_to_jsonapi() { $parser = new JsonapiParser(new MetaParser()); - $jsonapi = $parser->parse($this->getArt4Jsonapi()); + $jsonapi = $parser->parse($this->getJsonapi()); $this->assertInstanceOf(Jsonapi::class, $jsonapi); $this->assertEquals('1.0', $jsonapi->getVersion()); @@ -27,23 +27,71 @@ public function it_converts_art4jsonapi_to_jsonapi() } /** - * @return \Art4\JsonApiClient\Jsonapi + * @test + * @dataProvider provideInvalidData + * + * @param mixed $invalidData */ - protected function getArt4Jsonapi() + public function it_throws_when_data_is_not_an_object($invalidData) { - $jsonapi = [ - 'jsonapi' => [ - 'version' => '1.0', - 'meta' => [ - 'copyright' => 'Copyright 2015 Example Corp.', - ], - ], - 'data' => [], + $parser = new JsonapiParser($this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidData); + } + + public function provideInvalidData(): array + { + return [ + [1], + [1.5], + [false], + [null], + ['foo'], + [[]], ]; + } + + /** + * @test + * @dataProvider provideInvalidVersionJsonapi + * + * @param mixed $invalidJsonapi + */ + public function it_throws_when_version_is_not_a_string($invalidJsonapi) + { + $parser = new JsonapiParser($this->createMock(MetaParser::class)); - $manager = new Manager(); - $jsonApiItem = $manager->parse(json_encode($jsonapi)); + $this->expectException(ValidationException::class); + + $parser->parse($invalidJsonapi); + } + + public function provideInvalidVersionJsonapi(): array + { + return [ + [json_decode('{"version": 1}', false)], + [json_decode('{"version": 1.5}', false)], + [json_decode('{"version": false}', false)], + [json_decode('{"version": null}', false)], + [json_decode('{"version": []}', false)], + [json_decode('{"version": {}}', false)], + ]; + } + + /** + * @return \stdClass + */ + protected function getJsonapi() + { + $data = [ + 'version' => '1.0', + 'meta' => [ + 'copyright' => 'Copyright 2015 Example Corp.', + ], + ]; - return $jsonApiItem->get('jsonapi'); + return json_decode(json_encode($data), false); } } diff --git a/tests/Parsers/LinksParserTest.php b/tests/Parsers/LinksParserTest.php index a2e8d9f..0813f4f 100644 --- a/tests/Parsers/LinksParserTest.php +++ b/tests/Parsers/LinksParserTest.php @@ -2,7 +2,7 @@ namespace Swis\JsonApi\Client\Tests\Parsers; -use Art4\JsonApiClient\Utils\Manager; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Link; use Swis\JsonApi\Client\Links; use Swis\JsonApi\Client\Meta; @@ -15,13 +15,13 @@ class LinksParserTest extends AbstractTest /** * @test */ - public function it_converts_art4links_to_links() + public function it_converts_data_to_links() { $parser = new LinksParser(new MetaParser()); - $links = $parser->parse($this->getArt4Links()->asArray()); + $links = $parser->parse($this->getLinks()); $this->assertInstanceOf(Links::class, $links); - $this->assertCount(3, $links->toArray()); + $this->assertCount(4, $links->toArray()); /** @var \Swis\JsonApi\Client\Link $link */ $link = $links->self; @@ -41,32 +41,68 @@ public function it_converts_art4links_to_links() $this->assertInstanceOf(Link::class, $link); $this->assertEquals('http://example.com/articles?page[offset]=10', $link->getHref()); $this->assertNull($link->getMeta()); + + /** @var null $link */ + $link = $links->related; + $this->assertNull($link); } /** - * @return \Art4\JsonApiClient\ErrorCollection + * @test + * @dataProvider provideInvalidData + * + * @param mixed $invalidData */ - protected function getArt4Links() + public function it_throws_when_link_is_not_a_string_object_or_null($invalidData) { - $links = [ - 'links' => [ - 'self' => [ - 'href' => 'http://example.com/articles', - 'meta' => [ - 'copyright' => 'Copyright 2015 Example Corp.', - ], - ], - 'next' => [ - 'href' => 'http://example.com/articles?page[offset]=2', + $parser = new LinksParser($this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidData); + } + + public function provideInvalidData(): array + { + return [ + [[1]], + [[1.5]], + [[false]], + [[[]]], + ]; + } + + /** + * @test + */ + public function it_throws_when_link_does_not_have_href_property() + { + $parser = new LinksParser($this->createMock(MetaParser::class)); + + $this->expectException(ValidationException::class); + + $parser->parse(json_decode('[{}]', false)); + } + + /** + * @return \stdClass + */ + protected function getLinks() + { + $data = [ + 'self' => [ + 'href' => 'http://example.com/articles', + 'meta' => [ + 'copyright' => 'Copyright 2015 Example Corp.', ], - 'last' => 'http://example.com/articles?page[offset]=10', ], - 'data' => [], + 'next' => [ + 'href' => 'http://example.com/articles?page[offset]=2', + ], + 'last' => 'http://example.com/articles?page[offset]=10', + 'related' => null, ]; - $manager = new Manager(); - $jsonApiItem = $manager->parse(json_encode($links)); - - return $jsonApiItem->get('links'); + return json_decode(json_encode($data), false); } } diff --git a/tests/Parsers/MetaParserTest.php b/tests/Parsers/MetaParserTest.php index 89d7cd0..bb2a5c2 100644 --- a/tests/Parsers/MetaParserTest.php +++ b/tests/Parsers/MetaParserTest.php @@ -2,7 +2,7 @@ namespace Swis\JsonApi\Client\Tests\Parsers; -use Art4\JsonApiClient\Utils\Manager; +use Swis\JsonApi\Client\Exceptions\ValidationException; use Swis\JsonApi\Client\Meta; use Swis\JsonApi\Client\Parsers\MetaParser; use Swis\JsonApi\Client\Tests\AbstractTest; @@ -12,10 +12,10 @@ class MetaParserTest extends AbstractTest /** * @test */ - public function it_converts_art4meta_to_meta() + public function it_converts_data_to_meta() { $parser = new MetaParser(); - $meta = $parser->parse($this->getArt4Meta()); + $meta = $parser->parse($this->getMeta()); $this->assertInstanceOf(Meta::class, $meta); $this->assertCount(1, $meta->toArray()); @@ -23,20 +23,41 @@ public function it_converts_art4meta_to_meta() } /** - * @return \Art4\JsonApiClient\Meta + * @test + * @dataProvider provideInvalidData + * + * @param mixed $invalidData */ - protected function getArt4Meta() + public function it_throws_when_data_is_not_an_object($invalidData) + { + $parser = new MetaParser(); + + $this->expectException(ValidationException::class); + + $parser->parse($invalidData); + } + + public function provideInvalidData(): array { - $meta = [ - 'meta' => [ - 'copyright' => 'Copyright 2015 Example Corp.', - ], - 'data' => [], + return [ + [1], + [1.5], + [false], + [null], + ['foo'], + [[]], ]; + } - $manager = new Manager(); - $jsonApiItem = $manager->parse(json_encode($meta)); + /** + * @return \stdClass + */ + protected function getMeta() + { + $data = [ + 'copyright' => 'Copyright 2015 Example Corp.', + ]; - return $jsonApiItem->get('meta'); + return json_decode(json_encode($data), false); } }