Skip to content

Commit 3ba8dc7

Browse files
authored
Add support for deepObject (#75)
1 parent f17a1dd commit 3ba8dc7

File tree

2 files changed

+211
-13
lines changed

2 files changed

+211
-13
lines changed

src/PSR7/Validators/SerializedParameter.php

+22-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace League\OpenAPIValidation\PSR7\Validators;
66

77
use cebe\openapi\spec\Parameter as CebeParameter;
8+
use cebe\openapi\spec\Schema;
89
use cebe\openapi\spec\Schema as CebeSchema;
910
use cebe\openapi\spec\Type as CebeType;
1011
use League\OpenAPIValidation\Schema\Exception\ContentTypeMismatch;
@@ -16,6 +17,7 @@
1617
use const JSON_ERROR_NONE;
1718
use function explode;
1819
use function in_array;
20+
use function is_array;
1921
use function is_float;
2022
use function is_int;
2123
use function is_numeric;
@@ -33,6 +35,7 @@ final class SerializedParameter
3335
private const STYLE_FORM = 'form';
3436
private const STYLE_SPACE_DELIMITED = 'spaceDelimited';
3537
private const STYLE_PIPE_DELIMITED = 'pipeDelimited';
38+
private const STYLE_DEEP_OBJECT = 'deepObject';
3639
private const STYLE_DELIMITER_MAP = [
3740
self::STYLE_FORM => ',',
3841
self::STYLE_SPACE_DELIMITED => ' ',
@@ -135,7 +138,11 @@ private function castToSchemaType($value, ?string $type)
135138
}
136139

137140
if (($type === CebeType::ARRAY) && is_string($value)) {
138-
return $this->convertToSerializationStyle($value);
141+
return $this->convertToSerializationStyle($value, $this->schema);
142+
}
143+
144+
if (($type === CebeType::OBJECT) && is_array($value)) {
145+
return $this->convertToSerializationStyle($value, $this->schema);
139146
}
140147

141148
return $value;
@@ -146,13 +153,25 @@ private function castToSchemaType($value, ?string $type)
146153
*
147154
* @return mixed
148155
*/
149-
protected function convertToSerializationStyle($value)
156+
protected function convertToSerializationStyle($value, ?Schema $schema)
150157
{
151158
if ($this->explode === false
152159
&& in_array($this->style, [self::STYLE_FORM, self::STYLE_SPACE_DELIMITED, self::STYLE_PIPE_DELIMITED], true)) {
153160
$value = explode(self::STYLE_DELIMITER_MAP[$this->style], $value);
154161
foreach ($value as &$val) {
155-
$val = $this->castToSchemaType($val, $this->schema->items->type ?? null);
162+
$val = $this->castToSchemaType($val, $schema->items->type ?? null);
163+
}
164+
165+
return $value;
166+
}
167+
168+
if ($schema && $this->explode === true && $this->style === self::STYLE_DEEP_OBJECT) {
169+
foreach ($value as $key => &$val) {
170+
if (is_array($val)) {
171+
$val = $this->convertToSerializationStyle($val, $schema->properties[$key] ?? null);
172+
} else {
173+
$val = $this->castToSchemaType($val, $schema->properties[$key]->type ?? null);
174+
}
156175
}
157176

158177
return $value;

tests/FromCommunity/IssueWithQueryArrayTest.php

+189-10
Original file line numberDiff line numberDiff line change
@@ -5,69 +5,247 @@
55
namespace League\OpenAPIValidation\Tests\FromCommunity;
66

77
use GuzzleHttp\Psr7\ServerRequest;
8+
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidParameter;
9+
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidQueryArgs;
810
use League\OpenAPIValidation\PSR7\ValidatorBuilder;
11+
use League\OpenAPIValidation\Schema\Exception\TypeMismatch;
912
use PHPUnit\Framework\TestCase;
1013

1114
final class IssueWithQueryArrayTest extends TestCase
1215
{
1316
public function testConvertFormIntegerArray() : void
1417
{
15-
$validator = (new ValidatorBuilder())->fromYaml($this->makeYaml('form', 'integer', 'int32'))->getServerRequestValidator();
18+
$validator = (new ValidatorBuilder())->fromYaml($this->makeArrayYaml('form', 'integer', 'int32'))->getServerRequestValidator();
1619
$validator->validate($this->makeRequest('form', 'integer'));
1720
$this->addToAssertionCount(1);
1821
}
1922

2023
public function testConvertFormNumberArray() : void
2124
{
22-
$validator = (new ValidatorBuilder())->fromYaml($this->makeYaml('form', 'number', 'float'))->getServerRequestValidator();
25+
$validator = (new ValidatorBuilder())->fromYaml($this->makeArrayYaml('form', 'number', 'float'))->getServerRequestValidator();
2326
$validator->validate($this->makeRequest('form', 'number'));
2427
$this->addToAssertionCount(1);
2528
}
2629

2730
public function testConvertFormIntegerArrayToStringArray() : void
2831
{
29-
$validator = (new ValidatorBuilder())->fromYaml($this->makeYaml('form', 'string', 'int32'))->getServerRequestValidator();
32+
$validator = (new ValidatorBuilder())->fromYaml($this->makeArrayYaml('form', 'string', 'int32'))->getServerRequestValidator();
3033
$validator->validate($this->makeRequest('form', 'integer'));
3134
$this->addToAssertionCount(1);
3235
}
3336

3437
public function testConvertFormStringArray() : void
3538
{
36-
$validator = (new ValidatorBuilder())->fromYaml($this->makeYaml('form', 'string', 'int32'))->getServerRequestValidator();
39+
$validator = (new ValidatorBuilder())->fromYaml($this->makeArrayYaml('form', 'string', 'int32'))->getServerRequestValidator();
3740
$validator->validate($this->makeRequest('form', 'string'));
3841
$this->addToAssertionCount(1);
3942
}
4043

4144
public function testConvertFormBooleanArray() : void
4245
{
43-
$validator = (new ValidatorBuilder())->fromYaml($this->makeYaml('form', 'boolean', 'int32'))->getServerRequestValidator();
46+
$validator = (new ValidatorBuilder())->fromYaml($this->makeArrayYaml('form', 'boolean', 'int32'))->getServerRequestValidator();
4447
$validator->validate($this->makeRequest('form', 'boolean'));
4548
$this->addToAssertionCount(1);
4649
}
4750

4851
public function testConvertFormIntegerArrayError() : void
4952
{
5053
$this->expectExceptionMessage('Value "id1,id2,id3" for argument "id" is invalid for Request [get /users]');
51-
$validator = (new ValidatorBuilder())->fromYaml($this->makeYaml('form', 'integer', 'int32'))->getServerRequestValidator();
54+
$validator = (new ValidatorBuilder())->fromYaml($this->makeArrayYaml('form', 'integer', 'int32'))->getServerRequestValidator();
5255
$validator->validate($this->makeRequest('form', 'string'));
53-
$this->addToAssertionCount(1);
5456
}
5557

5658
public function testConvertSpaceIntegerArray() : void
5759
{
58-
$validator = (new ValidatorBuilder())->fromYaml($this->makeYaml('spaceDelimited', 'integer', 'int32'))->getServerRequestValidator();
60+
$validator = (new ValidatorBuilder())->fromYaml($this->makeArrayYaml('spaceDelimited', 'integer', 'int32'))->getServerRequestValidator();
5961
$validator->validate($this->makeRequest('spaceDelimited', 'integer'));
6062
$this->addToAssertionCount(1);
6163
}
6264

6365
public function testConvertPipeIntegerArray() : void
6466
{
65-
$validator = (new ValidatorBuilder())->fromYaml($this->makeYaml('pipeDelimited', 'integer', 'int32'))->getServerRequestValidator();
67+
$validator = (new ValidatorBuilder())->fromYaml($this->makeArrayYaml('pipeDelimited', 'integer', 'int32'))->getServerRequestValidator();
6668
$validator->validate($this->makeRequest('pipeDelimited', 'integer'));
6769
$this->addToAssertionCount(1);
6870
}
6971

70-
protected function makeYaml(string $style, string $type, string $format) : string
72+
public function testConvertSingleLayerDeepObject() : void
73+
{
74+
$yaml = /** @lang yaml */
75+
<<<YAML
76+
openapi: 3.0.0
77+
info:
78+
title: Product import API
79+
version: '1.0'
80+
servers:
81+
- url: 'http://localhost:8000/api/v1'
82+
paths:
83+
/users:
84+
get:
85+
parameters:
86+
- in: query
87+
name: id
88+
required: true
89+
style: deepObject
90+
explode: true
91+
schema:
92+
type: object
93+
properties:
94+
before:
95+
type: integer
96+
format: int32
97+
after:
98+
type: integer
99+
format: int32
100+
responses:
101+
'200':
102+
description: A list of users
103+
YAML;
104+
$validator = (new ValidatorBuilder())->fromYaml($yaml)->getServerRequestValidator();
105+
$validator->validate($this->makeRequest('deepObject', 'integer'));
106+
$this->addToAssertionCount(1);
107+
}
108+
109+
public function testConvertSingleLayerDeepObjectError() : void
110+
{
111+
$yaml = /** @lang yaml */
112+
<<<YAML
113+
openapi: 3.0.0
114+
info:
115+
title: Product import API
116+
version: '1.0'
117+
servers:
118+
- url: 'http://localhost:8000/api/v1'
119+
paths:
120+
/users:
121+
get:
122+
parameters:
123+
- in: query
124+
name: id
125+
required: true
126+
style: deepObject
127+
explode: true
128+
schema:
129+
type: object
130+
properties:
131+
before:
132+
type: integer
133+
format: int32
134+
after:
135+
type: integer
136+
format: int32
137+
responses:
138+
'200':
139+
description: A list of users
140+
YAML;
141+
try {
142+
$validator = (new ValidatorBuilder())->fromYaml($yaml)->getServerRequestValidator();
143+
$validator->validate($this->makeRequest('deepObject', 'error'));
144+
} catch (InvalidQueryArgs $exception) {
145+
/** @var InvalidParameter $previous */
146+
$previous = $exception->getPrevious();
147+
/** @var TypeMismatch $previous */
148+
$previous = $previous->getPrevious();
149+
self::assertInstanceOf(TypeMismatch::class, $previous);
150+
self::assertEquals(['id', 'before'], $previous->dataBreadCrumb()->buildChain());
151+
}
152+
}
153+
154+
public function testConvertMultiLayerDeepObject() : void
155+
{
156+
$yaml = /** @lang yaml */
157+
<<<YAML
158+
openapi: 3.0.0
159+
info:
160+
title: Product import API
161+
version: '1.0'
162+
servers:
163+
- url: 'http://localhost:8000/api/v1'
164+
paths:
165+
/users:
166+
get:
167+
parameters:
168+
- in: query
169+
name: id
170+
required: true
171+
style: deepObject
172+
explode: true
173+
schema:
174+
type: object
175+
properties:
176+
before:
177+
type: object
178+
properties:
179+
first:
180+
type: object
181+
properties:
182+
second:
183+
type: integer
184+
format: int32
185+
after:
186+
type: integer
187+
format: int32
188+
responses:
189+
'200':
190+
description: A list of users
191+
YAML;
192+
$validator = (new ValidatorBuilder())->fromYaml($yaml)->getServerRequestValidator();
193+
$validator->validate($this->makeRequest('deepObject', 'deep'));
194+
$this->addToAssertionCount(1);
195+
}
196+
197+
public function testConvertMultiLayerDeepObjectError() : void
198+
{
199+
$yaml = /** @lang yaml */
200+
<<<YAML
201+
openapi: 3.0.0
202+
info:
203+
title: Product import API
204+
version: '1.0'
205+
servers:
206+
- url: 'http://localhost:8000/api/v1'
207+
paths:
208+
/users:
209+
get:
210+
parameters:
211+
- in: query
212+
name: id
213+
required: true
214+
style: deepObject
215+
explode: true
216+
schema:
217+
type: object
218+
properties:
219+
before:
220+
type: object
221+
properties:
222+
first:
223+
type: object
224+
properties:
225+
second:
226+
type: string
227+
format: date-time
228+
after:
229+
type: integer
230+
format: int32
231+
responses:
232+
'200':
233+
description: A list of users
234+
YAML;
235+
try {
236+
$validator = (new ValidatorBuilder())->fromYaml($yaml)->getServerRequestValidator();
237+
$validator->validate($this->makeRequest('deepObject', 'deep'));
238+
} catch (InvalidQueryArgs $exception) {
239+
/** @var InvalidParameter $previous */
240+
$previous = $exception->getPrevious();
241+
/** @var TypeMismatch $previous */
242+
$previous = $previous->getPrevious();
243+
self::assertInstanceOf(TypeMismatch::class, $previous);
244+
self::assertEquals(['id', 'before', 'first', 'second'], $previous->dataBreadCrumb()->buildChain());
245+
}
246+
}
247+
248+
protected function makeArrayYaml(string $style, string $type, string $format) : string
71249
{
72250
return $yaml = /** @lang yaml */
73251
<<<YAML
@@ -103,6 +281,7 @@ protected function makeRequest(string $style, string $type) : ServerRequest
103281
'form' => ['integer' => '1,2,3', 'string' => 'id1,id2,id3', 'boolean' => 'true,false', 'number' => '1.00,2.00,3.00'],
104282
'spaceDelimited' => ['integer' => '1 2 3', 'string' => 'id1 id2 id3', 'boolean' => 'true false', 'number' => '1.00 2.00 3.00'],
105283
'pipeDelimited' => ['integer' => '1|2|3', 'string' => 'id1|id2|id3', 'boolean' => 'true|false', 'number' => '1.00|2.00|3.00'],
284+
'deepObject' => ['integer' => ['before' => 10, 'after' => 1], 'deep' => ['before' => ['first' => ['second' => '10']], 'after' => 1], 'error' => ['before' => 'ten', 'after' => 'one']],
106285
];
107286
$request = new ServerRequest('GET', 'http://localhost:8000/api/v1/users');
108287
$request = $request->withQueryParams(['id' => $map[$style][$type]]);

0 commit comments

Comments
 (0)