Skip to content

Commit ec88278

Browse files
committed
Issue #2: indexes broken on deletion fixed
1 parent 8368bba commit ec88278

File tree

4 files changed

+147
-115
lines changed

4 files changed

+147
-115
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/build/
22
/vendor/
33
/composer.lock
4+
.phpunit.result.cache

composer.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
}
1313
],
1414
"require": {
15-
"php": "^7.1",
16-
"remorhaz/php-json-data": "^0.4.2",
17-
"remorhaz/php-json-pointer": "^0.5.1"
15+
"php": "^7.3",
16+
"remorhaz/php-json-data": "^0.5.2",
17+
"remorhaz/php-json-pointer": "^0.6.0"
1818
},
1919
"require-dev": {
20-
"phpunit/phpunit": "^7.0.1"
20+
"phpunit/phpunit": "^8.4.3",
21+
"infection/infection": "^0.14.2"
2122
},
2223
"autoload": {
2324
"psr-4": {

src/Patch.php

Lines changed: 127 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -2,164 +2,190 @@
22

33
namespace Remorhaz\JSON\Patch;
44

5-
use Remorhaz\JSON\Data\Exception as DataException;
6-
use Remorhaz\JSON\Data\ReaderInterface;
7-
use Remorhaz\JSON\Data\SelectorInterface;
8-
use Remorhaz\JSON\Pointer\Pointer;
5+
use Remorhaz\JSON\Data\Value\ArrayValueInterface;
6+
use Remorhaz\JSON\Data\Value\DecodedJson\NodeValueFactory as DecodedJsonNodeValueFactory;
7+
use Remorhaz\JSON\Data\Value\NodeValueInterface;
8+
use Remorhaz\JSON\Pointer\Processor\Processor;
9+
use Remorhaz\JSON\Pointer\Processor\Result\ResultInterface;
10+
use Remorhaz\JSON\Pointer\Query\QueryFactory;
11+
use Remorhaz\JSON\Pointer\Query\QueryInterface;
12+
use RuntimeException;
13+
use function is_string;
914

1015
class Patch
1116
{
1217

13-
private $inputSelector;
18+
private $queryFactory;
1419

15-
private $outputSelector;
20+
private $queryProcessor;
1621

17-
private $patchSelector;
22+
private $decodedJsonNodeValueFactory;
1823

19-
private $dataPointer;
24+
private $outputData;
2025

21-
private $patchPointer;
22-
23-
public function __construct(SelectorInterface $dataSelector)
26+
public function __construct(NodeValueInterface $inputData)
2427
{
25-
$this->setInputSelector($dataSelector);
28+
$this->outputData = $inputData;
29+
$this->queryFactory = QueryFactory::create();
30+
$this->queryProcessor = Processor::create();
31+
$this->decodedJsonNodeValueFactory = DecodedJsonNodeValueFactory::create();
2632
}
2733

28-
public function apply(SelectorInterface $patchSelector)
34+
public function apply(NodeValueInterface $patch)
2935
{
30-
$this
31-
->setPatchSelector($patchSelector)
32-
->getPatchSelector()
33-
->selectRoot();
34-
if (!$this->getPatchSelector()->isArray()) {
35-
throw new \RuntimeException("Patch must be an array");
36-
}
37-
$this->setOutputSelector($this->getInputSelector());
38-
$operationCount = $this
39-
->getPatchSelector()
40-
->getElementCount();
41-
for ($operationIndex = 0; $operationIndex < $operationCount; $operationIndex++) {
42-
$this->performOperation($operationIndex);
36+
if (!$patch instanceof ArrayValueInterface) {
37+
throw new RuntimeException("Patch must be an array");
4338
}
44-
$this->setInputSelector($this->getOutputSelector());
45-
return $this;
46-
}
47-
48-
public function getResult(): ReaderInterface
49-
{
50-
return $this->getOutputSelector();
51-
}
52-
53-
protected function setInputSelector(SelectorInterface $selector)
54-
{
55-
$this->inputSelector = $selector;
56-
return $this;
57-
}
58-
59-
protected function getInputSelector(): SelectorInterface
60-
{
61-
if (null === $this->inputSelector) {
62-
throw new \LogicException("Input selector is empty");
39+
foreach ($patch->createChildIterator() as $element) {
40+
$this->performOperation($element);
6341
}
64-
return $this->inputSelector;
65-
}
6642

67-
protected function setOutputSelector(SelectorInterface $selector)
68-
{
69-
$this->outputSelector = $selector;
7043
return $this;
7144
}
7245

73-
protected function getOutputSelector(): SelectorInterface
46+
protected function performOperation(NodeValueInterface $element)
7447
{
75-
if (null === $this->outputSelector) {
76-
throw new \LogicException("Output selector is empty");
48+
$operation = $this
49+
->queryProcessor
50+
->select($this->queryFactory->createQuery('/op'), $element)
51+
->decode();
52+
if (!is_string($operation)) {
53+
throw new RuntimeException("Invalid patch operation");
7754
}
78-
return $this->outputSelector;
79-
}
80-
81-
protected function performOperation(int $index)
82-
{
83-
$operation = $this->getPatchPointer()->read("/{$index}/op")->getAsString();
84-
$path = $this->getPatchPointer()->read("/{$index}/path")->getAsString();
55+
$pathQuery = $this->getOperationPath($element);
8556
switch ($operation) {
8657
case 'add':
87-
$valueReader = $this->getPatchPointer()->read("/{$index}/value");
88-
$this->getDataPointer()->add($path, $valueReader);
58+
$result = $this
59+
->queryProcessor
60+
->add(
61+
$pathQuery,
62+
$this->outputData,
63+
$this->getOperationValue($element)
64+
);
65+
$this->outputData = $this->getResultValue($result);
8966
break;
9067

9168
case 'remove':
92-
$this->getDataPointer()->remove($path);
69+
$result = $this
70+
->queryProcessor
71+
->delete($pathQuery, $this->outputData);
72+
$this->outputData = $this->getResultValue($result);
9373
break;
9474

9575
case 'replace':
96-
$valueReader = $this->getPatchPointer()->read("/{$index}/value");
97-
$this->getDataPointer()->replace($path, $valueReader);
76+
$result = $this
77+
->queryProcessor
78+
->replace(
79+
$pathQuery,
80+
$this->outputData,
81+
$this->getOperationValue($element)
82+
);
83+
$this->outputData = $this->getResultValue($result);
9884
break;
9985

10086
case 'test':
101-
$expectedValueReader = $this->getPatchPointer()->read("/{$index}/value");
102-
$actualValueReader = $this->getDataPointer()->read($path);
103-
try {
104-
// TODO: Make reader's test() method boolean and refactor pointer's test().
105-
$expectedValueReader->test($actualValueReader);
106-
} catch (DataException $e) {
107-
throw new \RuntimeException("Test operation failed", 0, $e);
87+
$result = $this
88+
->queryProcessor
89+
->select($pathQuery, $this->outputData);
90+
if (!$result->exists()) {
91+
throw new RuntimeException("Test operation failed");
10892
}
10993
break;
11094

11195
case 'copy':
112-
$from = $this->getPatchPointer()->read("/{$index}/from")->getAsString();
113-
$valueReader = $this->getDataPointer()->read($from);
114-
$this->getDataPointer()->add($path, $valueReader);
96+
$fromResult = $this
97+
->queryProcessor
98+
->select(
99+
$this->getOperationFrom($element),
100+
$this->outputData
101+
);
102+
$result = $this
103+
->queryProcessor
104+
->add(
105+
$pathQuery,
106+
$this->outputData,
107+
$this->getResultValue($fromResult)
108+
);
109+
$this->outputData = $this->getResultValue($result);
115110
break;
116111

117112
case 'move':
118-
$from = $this->getPatchPointer()->read("/{$index}/from")->getAsString();
119-
$valueReader = $this->getDataPointer()->read($from);
120-
$this
121-
->getDataPointer()
122-
->remove($from)
123-
->add($path, $valueReader);
113+
$fromPathQuery = $this->getOperationFrom($element);
114+
$fromResult = $this
115+
->queryProcessor
116+
->select($fromPathQuery, $this->outputData);
117+
$removeResult = $this
118+
->queryProcessor
119+
->delete($fromPathQuery, $this->outputData);
120+
$result = $this
121+
->queryProcessor
122+
->add(
123+
$pathQuery,
124+
$this->getResultValue($removeResult),
125+
$this->getResultValue($fromResult)
126+
);
127+
$this->outputData = $this->getResultValue($result);
124128
break;
125129

126130
default:
127-
throw new \RuntimeException("Unknown operation '{$operation}'");
131+
throw new RuntimeException("Unknown operation '{$operation}'");
128132
}
133+
129134
return $this;
130135
}
131136

132-
133-
protected function setPatchSelector(SelectorInterface $patchReader)
137+
private function getOperationPath(NodeValueInterface $operation): QueryInterface
134138
{
135-
$this->patchSelector = $patchReader;
136-
return $this;
137-
}
139+
$path = $this
140+
->queryProcessor
141+
->select($this->queryFactory->createQuery('/path'), $operation)
142+
->decode();
143+
if (!is_string($path)) {
144+
throw new RuntimeException("Invalid operation path");
145+
}
138146

147+
return $this
148+
->queryFactory
149+
->createQuery($path);
150+
}
139151

140-
protected function getPatchSelector(): SelectorInterface
152+
private function getOperationFrom(NodeValueInterface $operation): QueryInterface
141153
{
142-
if (null === $this->patchSelector) {
143-
throw new \LogicException("Patch reader is not set");
154+
$path = $this
155+
->queryProcessor
156+
->select($this->queryFactory->createQuery('/from'), $operation)
157+
->decode();
158+
if (!is_string($path)) {
159+
throw new RuntimeException("Invalid operation from");
144160
}
145-
return $this->patchSelector;
146-
}
147161

162+
return $this
163+
->queryFactory
164+
->createQuery($path);
165+
}
148166

149-
protected function getPatchPointer(): Pointer
167+
private function getOperationValue(NodeValueInterface $operation): NodeValueInterface
150168
{
151-
if (null === $this->patchPointer) {
152-
$this->patchPointer = new Pointer($this->getPatchSelector());
169+
$result = $this
170+
->queryProcessor
171+
->select($this->queryFactory->createQuery('/value'), $operation);
172+
173+
if (!$result->exists()) {
174+
throw new RuntimeException("Patch result not found");
153175
}
154-
return $this->patchPointer;
176+
177+
return $this->getResultValue($result);
155178
}
156179

180+
private function getResultValue(ResultInterface $result): NodeValueInterface
181+
{
182+
return $this
183+
->decodedJsonNodeValueFactory
184+
->createValue($result->decode());
185+
}
157186

158-
protected function getDataPointer(): Pointer
187+
public function getOutputData(): NodeValueInterface
159188
{
160-
if (null === $this->dataPointer) {
161-
$this->dataPointer = new Pointer($this->getInputSelector());
162-
}
163-
return $this->dataPointer;
189+
return $this->outputData;
164190
}
165191
}

tests/PatchTest.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
namespace Remorhaz\JSON\Test\Patch;
44

55
use PHPUnit\Framework\TestCase;
6-
use Remorhaz\JSON\Data\Reference\Selector;
7-
use Remorhaz\JSON\Data\Reference\Writer;
6+
use Remorhaz\JSON\Data\Export\ValueDecoder;
7+
use Remorhaz\JSON\Data\Export\ValueEncoder;
8+
use Remorhaz\JSON\Data\Value\DecodedJson\NodeValueFactory as DecodedNodeValueFactory;
89
use Remorhaz\JSON\Patch\Patch;
910

1011
class PatchTest extends TestCase
@@ -17,12 +18,15 @@ class PatchTest extends TestCase
1718
* @param mixed $expectedData
1819
* @dataProvider providerValidSpecPatch_Result
1920
*/
20-
public function testApply_ValidSpecPatch_Applied($data, array $patchData, $expectedData)
21+
public function testApply_ValidSpecPatch_Applied($data, array $patchData, $expectedData): void
2122
{
22-
$dataWriter = new Writer($data);
23-
$patchDataSelector = new Selector($patchData);
24-
(new Patch($dataWriter))->apply($patchDataSelector);
25-
$this->assertEquals($expectedData, $data);
23+
$decodedNodeValueFactory = DecodedNodeValueFactory::create();
24+
$dataValue = $decodedNodeValueFactory->createValue($data);
25+
$patchDataValue = $decodedNodeValueFactory->createValue($patchData);
26+
$patch = new Patch($dataValue);
27+
$patch->apply($patchDataValue);
28+
$actualData = (new ValueDecoder)->exportValue($patch->getOutputData());
29+
$this->assertEquals($expectedData, $actualData);
2630
}
2731

2832

@@ -54,7 +58,7 @@ public function providerValidSpecPatch_Result(): array
5458
* @dataProvider providerInvalidPatch
5559
* @expectedException \RuntimeException
5660
*/
57-
public function testApply_InvalidSpecPatch_ExceptionThrown($data, array $patchData)
61+
public function testApply_InvalidSpecPatch_ExceptionThrown($data, array $patchData): void
5862
{
5963
$dataWriter = new Writer($data);
6064
$patchDataSelector = new Selector($patchData);
@@ -88,7 +92,7 @@ public function providerInvalidSpecPatch(): array
8892
* @param mixed $expectedData
8993
* @dataProvider providerValidPatch_Result
9094
*/
91-
public function testApply_ValidPatch_Applied($data, array $patchData, $expectedData)
95+
public function testApply_ValidPatch_Applied($data, array $patchData, $expectedData): void
9296
{
9397
$dataWriter = new Writer($data);
9498
$patchDataSelector = new Selector($patchData);
@@ -124,7 +128,7 @@ public function providerValidPatch_Result(): array
124128
* @dataProvider providerInvalidPatch
125129
* @expectedException \RuntimeException
126130
*/
127-
public function testApply_InvalidPatch_ExceptionThrown($data, array $patchData)
131+
public function testApply_InvalidPatch_ExceptionThrown($data, array $patchData): void
128132
{
129133
$dataWriter = new Writer($data);
130134
$patchDataSelector = new Selector($patchData);

0 commit comments

Comments
 (0)