Skip to content

Commit de431b5

Browse files
authored
Merge pull request #64 from swisnl/feature/validate-unique-resources
Throw a ValidationException when document contains duplicate resources
2 parents f23b056 + 975762f commit de431b5

File tree

3 files changed

+86
-2
lines changed

3 files changed

+86
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99
### Changed
1010

1111
* Reworded `ValidationException` messages to align them with the wordings used in the specification.
12+
* `DocumentParser` throws a `ValidationException` when it encounters duplicate resources.
1213

1314
## [1.0.0-beta.3] - 2019-09-30
1415

src/Parsers/DocumentParser.php

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ private function getDocument($data): DocumentInterface
160160
$allItems = Collection::wrap($document->getData())
161161
->concat($document->getIncluded());
162162

163+
$duplicateItems = $this->getDuplicateItems($allItems);
164+
165+
if ($duplicateItems->isNotEmpty()) {
166+
throw new ValidationException(sprintf('Resources MUST be unique based on their `type` and `id`, %d duplicate(s) found.', $duplicateItems->count()));
167+
}
168+
163169
$this->linkRelationships($allItems);
164170

165171
return $document;
@@ -170,8 +176,7 @@ private function getDocument($data): DocumentInterface
170176
*/
171177
private function linkRelationships(Collection $items): void
172178
{
173-
// N.B. We reverse the items to make sure the first item in the collection takes precedence
174-
$keyedItems = $items->reverse()->keyBy(
179+
$keyedItems = $items->keyBy(
175180
function (ItemInterface $item) {
176181
return $this->getItemKey($item);
177182
}
@@ -229,4 +234,46 @@ private function getItemKey(ItemInterface $item): string
229234
{
230235
return sprintf('%s:%s', $item->getType(), $item->getId());
231236
}
237+
238+
/**
239+
* @param \Swis\JsonApi\Client\Collection $items
240+
*
241+
* @return \Swis\JsonApi\Client\Collection
242+
*/
243+
private function getDuplicateItems(Collection $items): Collection
244+
{
245+
$valueRetriever = function (ItemInterface $item) {
246+
return $this->getItemKey($item);
247+
};
248+
249+
// Collection->duplicates was introduced in Laravel 5.8
250+
if (method_exists($items, 'duplicates')) {
251+
return $items->duplicates($valueRetriever);
252+
}
253+
254+
/*
255+
* Duplicates code copied, and simplified for our use case, from Laravel 6.
256+
*
257+
* @see https://github.com/laravel/framework/blob/v6.1.0/src/Illuminate/Support/Collection.php#L275
258+
*/
259+
$values = $items->map($valueRetriever);
260+
261+
$uniqueValues = $values->unique();
262+
263+
$compare = static function ($a, $b) {
264+
return $a === $b;
265+
};
266+
267+
$duplicates = new Collection();
268+
269+
foreach ($values as $key => $value) {
270+
if ($uniqueValues->isNotEmpty() && $compare($value, $uniqueValues->first())) {
271+
$uniqueValues->shift();
272+
} else {
273+
$duplicates[$key] = $value;
274+
}
275+
}
276+
277+
return $duplicates;
278+
}
232279
}

tests/Parsers/DocumentParserTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,42 @@ public function provideInvalidIncluded(): array
180180
];
181181
}
182182

183+
/**
184+
* @test
185+
*/
186+
public function it_throws_when_it_finds_duplicate_resources()
187+
{
188+
$parser = $this->getDocumentParser();
189+
190+
$this->expectException(ValidationException::class);
191+
$this->expectExceptionMessage('Resources MUST be unique based on their `type` and `id`, 1 duplicate(s) found.');
192+
193+
$parser->parse(
194+
json_encode(
195+
[
196+
'data' => [
197+
[
198+
'type' => 'master',
199+
'id' => '1',
200+
'attributes' => [
201+
'foo' => 'bar',
202+
],
203+
],
204+
],
205+
'included' => [
206+
[
207+
'type' => 'master',
208+
'id' => '1',
209+
'attributes' => [
210+
'foo' => 'bar',
211+
],
212+
],
213+
],
214+
]
215+
)
216+
);
217+
}
218+
183219
/**
184220
* @test
185221
*/

0 commit comments

Comments
 (0)