Skip to content

Commit eef7f8a

Browse files
authored
Merge pull request #44 from membrane-php/accept-numeric-keys
Silently cast numeric property keys to string
2 parents 4b5863c + 20bfee0 commit eef7f8a

10 files changed

Lines changed: 84 additions & 73 deletions

File tree

composer.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@
1515
}
1616
},
1717
"require": {
18-
"php": "^8.1.0",
19-
"symfony/yaml": "^4 || ^5 || ^6 || ^7",
20-
"devizzent/cebe-php-openapi": "^1.1.2"
18+
"php": "^8.4.0",
19+
"symfony/yaml": "^6 || ^7",
20+
"devizzent/cebe-php-openapi": "^1.1.5"
2121
},
2222
"require-dev": {
23-
"phpunit/phpunit": "^10.5.36",
24-
"phpstan/phpstan": "^1.12.6",
25-
"squizlabs/php_codesniffer": "^3.5.4",
26-
"mikey179/vfsstream": "^1.6.7",
27-
"infection/infection": "^0.29.7"
23+
"phpunit/phpunit": "^12.5.11",
24+
"phpstan/phpstan": "^1.12.33",
25+
"squizlabs/php_codesniffer": "^4.0.1",
26+
"mikey179/vfsstream": "^v1.6.12",
27+
"infection/infection": "^0.32.6"
2828
},
2929
"config": {
3030
"allow-plugins": {

src/Exception/InvalidOpenAPI.php

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ public static function parameterMissingName(Identifier $identifier): self
185185
public static function parameterMissingLocation(Identifier $identifier): self
186186
{
187187
$message = <<<TEXT
188-
$identifier
188+
$identifier
189189
This Parameter MUST have an "in" field specifying their location.
190190
Its value MUST be "path", "query", "header" or "cookie".
191191
TEXT;
@@ -201,7 +201,7 @@ public static function parameterInvalidLocation(Identifier $identifier): self
201201
public static function parameterMissingRequired(Identifier $identifier): self
202202
{
203203
$message = <<<TEXT
204-
$identifier
204+
$identifier
205205
If the parameter location is "path", this property is REQUIRED and its value MUST be true.
206206
TEXT;
207207

@@ -211,7 +211,7 @@ public static function parameterMissingRequired(Identifier $identifier): self
211211
public static function parameterInvalidStyle(Identifier $identifier): self
212212
{
213213
$message = <<<TEXT
214-
$identifier
214+
$identifier
215215
This Parameter has an invalid value for its "style" field.
216216
TEXT;
217217

@@ -222,7 +222,7 @@ public static function parameterIncompatibleStyle(Identifier $identifier): self
222222
{
223223
$message = <<<TEXT
224224
$identifier
225-
This Parameter has an incompatible combination of "style" and "in".
225+
This Parameter has an incompatible combination of "style" and "in".
226226
TEXT;
227227

228228
return new self($message);
@@ -425,25 +425,13 @@ public static function mustSpecifyItemsForArrayType(
425425
return new self($message);
426426
}
427427

428-
public static function mustHaveStringKeys(
429-
Identifier $identifier,
430-
string $keyword,
431-
): self {
432-
$message = <<<TEXT
433-
$identifier
434-
$keyword MUST be specified an array with string keys
435-
TEXT;
436-
437-
return new self($message);
438-
}
439-
440428
public static function responseCodeMustBeNumericOrDefault(
441429
Identifier $identifier,
442430
string $code,
443431
): self {
444432
$message = <<<TEXT
445433
$identifier
446-
Response code MUST be numeric, or "default".
434+
Response code MUST be numeric, or "default".
447435
"$code" is invalid.
448436
TEXT;
449437

src/ValueObject/Valid/V30/Keywords.php

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -423,16 +423,10 @@ private function validateProperties(?array $properties): array
423423

424424
$result = [];
425425
foreach ($properties as $key => $subSchema) {
426-
if (!is_string($key)) {
427-
throw InvalidOpenAPI::mustHaveStringKeys(
428-
$this->getIdentifier(),
429-
'properties',
430-
);
431-
}
432-
433-
$result[$key] = new Schema(
426+
// json_decode casts numeric string keys to numbers
427+
$result[(string) $key] = new Schema(
434428
$this->getIdentifier()->append("properties($key)"),
435-
$subSchema
429+
$subSchema,
436430
);
437431
}
438432

src/ValueObject/Valid/V31/Keywords.php

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -445,16 +445,10 @@ private function validateProperties(?array $properties): array
445445

446446
$result = [];
447447
foreach ($properties as $key => $subSchema) {
448-
if (!is_string($key)) {
449-
throw InvalidOpenAPI::mustHaveStringKeys(
450-
$this->getIdentifier(),
451-
'properties',
452-
);
453-
}
454-
455-
$result[$key] = new Schema(
448+
// json_decode casts numeric string keys to numbers
449+
$result[(string) $key] = new Schema(
456450
$this->getIdentifier()->append("properties($key)"),
457-
$subSchema
451+
$subSchema,
458452
);
459453
}
460454

tests/MembraneReaderTest.php

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Membrane\OpenAPIReader\Factory\V30\FromCebe;
1515
use Membrane\OpenAPIReader\MembraneReader;
1616
use Membrane\OpenAPIReader\Tests\Fixtures\Helper\OpenAPIProvider;
17-
use Membrane\OpenAPIReader\Tests\Fixtures\Helper\PartialHelper;
1817
use Membrane\OpenAPIReader\ValueObject\Partial;
1918
use Membrane\OpenAPIReader\ValueObject\Valid;
2019
use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method;
@@ -271,8 +270,10 @@ public function itResolvesExternalReferencesFromAbsoluteFilePath(
271270

272271
#[Test]
273272
#[DataProvider('provideOpenAPIWithExternalReference')]
274-
public function itCannotResolveExternalReferenceFromString(string $openAPIString): void
275-
{
273+
public function itCannotResolveExternalReferenceFromString(
274+
string $openAPIString,
275+
string $_,
276+
): void {
276277
self::expectExceptionObject(CannotRead::cannotResolveExternalReferencesFromString());
277278

278279
(new MembraneReader([OpenAPIVersion::Version_3_0]))
@@ -373,6 +374,62 @@ public function itReadsRealExamples(
373374
self::assertEquals($expected, $actual);
374375
}
375376

377+
#[Test]
378+
public function itReadsStringNumericKeysAsStrings(): void
379+
{
380+
$sut = new MembraneReader([
381+
OpenAPIVersion::Version_3_0,
382+
OpenAPIVersion::Version_3_1,
383+
]);
384+
385+
$api = json_encode([
386+
'openapi' => '3.0.3',
387+
'info' => [
388+
'title' => 'Numeric Property Keys',
389+
'version' => '1.0.0',
390+
],
391+
'paths' => ['/foo' => ['parameters' => [[
392+
'name' => 'object-with-numeric-keys',
393+
'in' => 'query',
394+
'schema' => [
395+
'type' => 'object',
396+
'properties' => [
397+
'1' => ['type' => 'string'],
398+
]
399+
]
400+
]]]]
401+
]);
402+
403+
$expected = OpenAPI::fromPartial(new Partial\OpenAPI(
404+
openAPI: '3.0.3',
405+
title: 'Numeric Property Keys',
406+
version: '1.0.0',
407+
paths: [
408+
new Partial\PathItem(
409+
path: '/foo',
410+
parameters: [
411+
new Partial\Parameter(
412+
name: 'object-with-numeric-keys',
413+
in: 'query',
414+
schema: new Partial\Schema(
415+
type: 'object',
416+
properties: [
417+
'1' => new Partial\Schema(type: 'string'),
418+
],
419+
)
420+
),
421+
]
422+
)
423+
]
424+
));
425+
426+
$actual = $sut->readFromString($api, FileFormat::Json);
427+
428+
429+
430+
self::assertEquals($expected, $actual);
431+
}
432+
376433
public static function provideInvalidFormatting(): Generator
377434
{
378435
yield 'Empty string to be interpreted as json' => ['', FileFormat::Json];

tests/ReaderTest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,10 @@ public function itResolvesExternalReferencesFromAbsoluteFilePath(
447447

448448
#[Test]
449449
#[DataProvider('provideOpenAPIWithExternalReference')]
450-
public function itCannotResolveExternalReferenceFromString(string $openAPIString,): void
451-
{
450+
public function itCannotResolveExternalReferenceFromString(
451+
string $openAPIString,
452+
string $_,
453+
): void {
452454
self::expectExceptionObject(CannotRead::cannotResolveExternalReferencesFromString());
453455

454456
(new Reader([OpenAPIVersion::Version_3_0]))

tests/ValueObject/Valid/V30/SchemaTest.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -172,15 +172,6 @@ public static function provideInvalidSchemas(): Generator
172172
new Partial\Schema(type: 'invalid'),
173173
];
174174

175-
yield 'properties list' => [
176-
InvalidOpenAPI::mustHaveStringKeys(
177-
new Identifier('properties list'),
178-
'properties',
179-
),
180-
new Identifier('properties list'),
181-
new Partial\Schema(properties: [new Partial\Schema()]),
182-
];
183-
184175
yield 'negative maxLength' => [
185176
InvalidOpenAPI::keywordMustBeNonNegativeInteger(
186177
new Identifier('negative maxLength'),

tests/ValueObject/Valid/V31/SchemaTest.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -172,15 +172,6 @@ public static function provideInvalidSchemas(): Generator
172172
new Partial\Schema(type: 'invalid'),
173173
];
174174

175-
yield 'properties list' => [
176-
InvalidOpenAPI::mustHaveStringKeys(
177-
new Identifier('properties list'),
178-
'properties',
179-
),
180-
new Identifier('properties list'),
181-
new Partial\Schema(properties: [new Partial\Schema()]),
182-
];
183-
184175
yield 'negative maxLength' => [
185176
InvalidOpenAPI::keywordMustBeNonNegativeInteger(
186177
new Identifier('negative maxLength'),

tests/fixtures/Helper/PartialHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public static function createOperation(
114114
?string $operationId = 'test-id',
115115
array $servers = [],
116116
array $parameters = [],
117-
RequestBody $requestBody = null,
117+
?RequestBody $requestBody = null,
118118
array $responses = [],
119119
): Operation {
120120
return new Operation(

tests/fixtures/ProvidesInvalidatedSchemas.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ public static function forV3X(): Generator
3232
new Partial\Schema(type: 'invalid'),
3333
];
3434

35-
yield 'properties without string keys' => [
36-
InvalidOpenAPI::mustHaveStringKeys($identifier, 'properties'),
37-
$identifier,
38-
new Partial\Schema(properties: [new Partial\Schema()]),
39-
];
40-
4135
yield 'negative maxLength' => [
4236
InvalidOpenAPI::keywordMustBeNonNegativeInteger($identifier, 'maxLength'),
4337
$identifier,

0 commit comments

Comments
 (0)