8000 [Bugfix] Fix alternative decoding type for update resource requests · CodingSeo/laravel-json-api@32b781d · GitHub
[go: up one dir, main page]

Skip to content

Commit 32b781d

Browse files
committed
[Bugfix] Fix alternative decoding type for update resource requests
1 parent b47b766 commit 32b781d

File tree

8 files changed

+166
-33
lines changed

8 files changed

+166
-33
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Allow soft delete attribute path to use dot notation.
1212
### Fixed
1313
- [#347](https://github.com/cloudcreativity/laravel-json-api/issues/347)
1414
Update `zend-diactoros` dependency.
15+
- [#369](https://github.com/cloudcreativity/laravel-json-api/issues/369)
16+
Fix using an alternative decoding type for update (`PATCH`) requests.
1517

1618
## [1.1.0] - 2019-04-12
1719

src/Http/Requests/UpdateResource.php

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
namespace CloudCreativity\LaravelJsonApi\Http\Requests;
1919

20+
use CloudCreativity\LaravelJsonApi\Contracts\Validation\ValidatorFactoryInterface;
2021
use CloudCreativity\LaravelJsonApi\Contracts\Validators\ValidatorProviderInterface;
2122
use CloudCreativity\LaravelJsonApi\Exceptions\ValidationException;
2223
use CloudCreativity\LaravelJsonApi\Object\Document;
@@ -69,7 +70,7 @@ protected function validateQuery()
6970
*/
7071
protected function validateDocument()
7172
{
72-
$document = $this->decodeOrFail();
73+
$document = $this->decode();
7374
$validators = $this->getValidators();
7475

7576
/** Pre-1.0 validators */
@@ -78,12 +79,10 @@ protected function validateDocument()
7879
return;
7980
}
8081

81-
/** Check the document is compliant with the JSON API spec. */
82-
$this->passes($this->factory->createExistingResourceDocumentValidator(
83-
$document,
84-
$this->getResourceType(),
85-
$this->getResourceId()
86-
));
82+
/** If there is a decoded JSON API document, check it complies with the spec. */
83+
if ($document) {
84+
$this->validateDocumentCompliance($document);
85+
}
8786

8887
if ($validators) {
8988
$this->passes(
@@ -92,6 +91,24 @@ protected function validateDocument()
9291
}
9392
}
9493

94+
95+
/**
96+
* Validate the JSON API document complies with the spec.
97+
*
98+
* @param object $document
99+
* @return void
100+
*/
101+
protected function validateDocumentCompliance($document): void
102+
{
103+
$this->passes(
104+
$this->factory->createExistingResourceDocumentValidator(
105+
$document,
106+
$this->getResourceType(),
107+
$this->getResourceId()
108+
)
109+
);
110+
}
111+
95112
/**
96113
* @param ValidatorProviderInterface $validators
97114
* @param $document

tests/dummy/app/JsonApi/Avatars/Adapter.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,32 @@ public function create(array $document, EncodingParametersInterface $parameters)
5353
return parent::create(compact('data'), $parameters);
5454
}
5555

56+
/**
57+
* @param Avatar $record
58+
* @param array $document
59+
* @param EncodingParametersInterface $parameters
60+
* @return mixed
61+
*/
62+
public function update($record, array $document, EncodingParametersInterface $parameters)
63+
{
64+
if ($this->didDecode('application/vnd.api+json')) {
65+
return parent::update($record, $document, $parameters);
66+
}
67+
68+
$path = request()->file('avatar')->store('avatars');
69+
70+
$data = [
71+
'type' => 'avatars',
72+
'id' => $record->getRouteKey(),
73+
'attributes' => [
74+
'path' => $path,
75+
'media-type' => Storage::disk('local')->mimeType($path),
76+
],
77+
];
78+
79+
return parent::update($record, compact('data'), $parameters);
80+
}
81+
5682
/**
5783
* @param Avatar $avatar
5884
* @return void
@@ -70,5 +96,4 @@ protected function filter($query, Collection $filters)
7096
// TODO: Implement filter() method.
7197
}
7298

73-
7499
}

tests/dummy/app/JsonApi/Avatars/ContentNegotiator.php

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717

1818
namespace DummyApp\JsonApi\Avatars;
1919

20-
use CloudCreativity\LaravelJsonApi\Codec\Decoding;
21-
use CloudCreativity\LaravelJsonApi\Codec\DecodingList;
2220
use CloudCreativity\LaravelJsonApi\Codec\EncodingList;
2321
use CloudCreativity\LaravelJsonApi\Http\ContentNegotiator as BaseContentNegotiator;
2422
use DummyApp\Avatar;
@@ -27,6 +25,14 @@
2725
class ContentNegotiator extends BaseContentNegotiator
2826
{
2927

28+
/**
29+
* @var array
30+
*/
31+
protected $decoding = [
32+
'multipart/form-data' => FileDecoder::class,
33+
'multipart/form-data; boundary=*' => FileDecoder::class,
34+
];
35+
3036
/**
3137
* @param Avatar|null $avatar
3238
* @return EncodingList
@@ -40,16 +46,4 @@ protected function encodingsForOne(?Avatar $avatar): EncodingList
4046
->when($this->request->isMethod('GET'), $mediaType);
4147
}
4248

43-
/**
44-
* @param Avatar|null $avatar
45-
* @return DecodingList
46-
*/
47-
protected function decodingsForResource(?Avatar $avatar): DecodingList
48-
{
49-
return $this
50-
->decodingMediaTypes()
51-
->when(is_null($avatar), Decoding::create('multipart/form-data', new FileDecoder()))
52-
->when(is_null($avatar), Decoding::create('multipart/form-data; boundary=*', new FileDecoder()));
53-
}
54-
5549
}

tests/dummy/app/JsonApi/Avatars/Validators.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ public function create(array $document): ValidatorInterface
5050
);
5151
}
5252

53+
/**
54+
* @inheritdoc
55+
*/
56+
public function update($record, array $document): ValidatorInterface
57+
{
58+
if ($this->didNotDecode('application/vnd.api+json')) {
59+
return $this->createValidator($document, ['avatar' => 'required']);
60+
}
61+
62+
return parent::update($record, $document);
63+
}
64+
5365
/**
5466
* Get resource validation rules.
5567
*

tests/dummy/tests/Feature/Avatars/CreateTest.php

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,6 @@
2626
class CreateTest extends TestCase
2727
{
2828

29-
/**
30-
* @return array
31-
*/
32-
public function multipartProvider(): array
33-
{
34-
return [
35-
['multipart/form-data'],
36-
['multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW'],
37-
];
38-
}
39-
4029
/**
4130
* Test that a user can upload an avatar to the API using a standard
4231
* HTML form post. This means our API must allow a non-JSON API content media type

tests/dummy/tests/Feature/Avatars/TestCase.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ public function fieldProvider(): array
5252
];
5353
}
5454

55+
/**
56+
* @return array
57+
*/
58+
public function multipartProvider(): array
59+
{
60+
return [
61+
'form-data' => ['multipart/form-data'],
62+
'form-data w/boundary' => ['multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW'],
63+
];
64+
}
65+
5566
/**
5667
* Get the expected JSON API resource for the avatar model.
5768
*
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
/**
3+
* Copyright 2019 Cloud Creativity Limited
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace DummyApp\Tests\Feature\Avatars;
19+
20+
use CloudCreativity\LaravelJsonApi\Testing\TestResponse;
21+
use DummyApp\Avatar;
22+
use DummyApp\User;
23+
use Illuminate\Http\UploadedFile;
24+
use Illuminate\Support\Facades\Storage;
25+
26+
class UpdateTest extends TestCase
27+
{
28+
29+
/**
30+
* @var Avatar
31+
*/
32+
private $avatar;
33+
34+
/**
35+
* @return void
36+
*/
37+
protected function setUp(): void
38+
{
39+
parent::setUp();
40+
$this->avatar = factory(Avatar::class)->create();
41+
}
42+
43+
/**
44+
* Test that a user can upload an avatar to the API using a standard
45+
* HTML form post. This means our API must allow a non-JSON API content media type
46+
* when updating the resource.
47+
*
48+
* @param string $contentType
49+
* @dataProvider multipartProvider
50+
*/
51+
public function test(string $contentType): void
52+
{
53+
$file = UploadedFile::fake()->create('avatar.jpg');
54+
55+
$expected = [
56+
'type' => 'avatars',
57+
'id' => (string) $this->avatar->getRouteKey(),
58+
'attributes' => ['media-type' => 'image/jpeg'],
59+
];
60+
61+
/** @var TestResponse $response */
62+
$response = $this->withoutExceptionHandling()->actingAs($this->avatar->user, 'api')->patch(
63+
"/api/v1/avatars/{$this->avatar->getRouteKey()}?include=user",
64+
['avatar' => $file],
65+
['Content-Type' => $contentType, 'Content-Length' => '1']
66+
);
67+
68+
$response
69+
->assertUpdated($expected)
70+
->assertIsIncluded('users', $this->avatar->user)
71+
->id();
72+
73+
$this->assertDatabaseHas('avatars', [
74+
'id' => $this->avatar->getKey(),
75+
'media_type' => 'image/jpeg',
76+
'user_id' => $this->avatar->user->getKey(),
77+
]);
78+
79+
$path = Avatar::whereKey($this->avatar->getKey())->value('path');
80+
81+
Storage::disk('local')->assertExists($path);
82+
}
83+
}

0 commit comments

Comments
 (0)
0