diff --git a/src/SpecBaseObject.php b/src/SpecBaseObject.php index ef93401..0d19661 100644 --- a/src/SpecBaseObject.php +++ b/src/SpecBaseObject.php @@ -12,6 +12,7 @@ use cebe\openapi\json\JsonPointer; use cebe\openapi\json\JsonReference; use cebe\openapi\spec\Reference; +use cebe\openapi\spec\Schema; use cebe\openapi\spec\Type; /** @@ -234,7 +235,15 @@ public function validate(): bool } $this->_recursingValidate = true; $valid = true; - foreach ($this->_properties as $v) { + $allowedFields = array_keys($this->attributes()); + if (static::class === Schema::class) { + $allowedFields[] = 'additionalProperties'; + } + foreach ($this->_properties as $k => $v) { + if ($allowedFields && !in_array($k, $allowedFields, true) && substr($k, 0, 2) !== 'x-') { + $valid = false; + $this->addError('Invalid field: "' . $k . '"'); + } if ($v instanceof SpecObjectInterface) { if (!$v->validate()) { $valid = false; @@ -275,7 +284,7 @@ public function getErrors(): array if (($pos = $this->getDocumentPosition()) !== null) { $errors = [ array_map(function ($e) use ($pos) { - return "[{$pos->getPointer()}] $e"; + return $pos->getPointer() ? "[{$pos->getPointer()}] $e" : $e; }, $this->_errors) ]; } else { diff --git a/tests/ReaderTest.php b/tests/ReaderTest.php index eee9595..59e7566 100644 --- a/tests/ReaderTest.php +++ b/tests/ReaderTest.php @@ -44,7 +44,12 @@ public function testReadYamlWithAnchors() $openApiFile = __DIR__ . '/spec/data/traits-mixins.yaml'; $openapi = \cebe\openapi\Reader::readFromYamlFile($openApiFile); - $this->assertApiContent($openapi); + $this->assertApiContent($openapi, [ + '[/paths/~1foo/put/responses/200] Invalid field: "schema"', + '[/paths/~1foo/put/responses/404] Invalid field: "schema"', + '[/paths/~1foo/put/responses/428] Invalid field: "schema"', + '[/paths/~1foo/put/responses/default] Invalid field: "schema"' + ]); $putOperation = $openapi->paths['/foo']->put; $this->assertEquals('create foo', $putOperation->description); @@ -77,11 +82,11 @@ public function testReadYamlWithAnchors() $this->assertEquals('uuid of the resource', $foo->properties['uuid']->description); } - private function assertApiContent(\cebe\openapi\spec\OpenApi $openapi) + private function assertApiContent(\cebe\openapi\spec\OpenApi $openapi, $expected = []) { $result = $openapi->validate(); - $this->assertEquals([], $openapi->getErrors()); - $this->assertTrue($result); + $this->assertEquals($expected, $openapi->getErrors()); + $expected ? $this->assertFalse($result) : $this->assertTrue($result); $this->assertEquals("3.0.0", $openapi->openapi); diff --git a/tests/spec/OpenApiTest.php b/tests/spec/OpenApiTest.php index 433bd35..f7001e4 100644 --- a/tests/spec/OpenApiTest.php +++ b/tests/spec/OpenApiTest.php @@ -233,4 +233,27 @@ public function testSpecs($openApiFile) } } + + public function testInvalidTopLevelField() + { + $openapi = new OpenApi([ + 'AAAAAcomponents' => [ + 'User' => [ + 'type' => 'object', + 'properties' => [ + 'id' => [ + 'type' => 'integer' + ], + ] + ] + ] + ]); + $this->assertFalse($openapi->validate()); + $this->assertEquals([ + 'Invalid field: "AAAAAcomponents"', + 'OpenApi is missing required property: openapi', + 'OpenApi is missing required property: info', + 'OpenApi is missing required property: paths', + ], $openapi->getErrors()); + } } diff --git a/tests/spec/PathTest.php b/tests/spec/PathTest.php index 2d47e3e..2464fec 100644 --- a/tests/spec/PathTest.php +++ b/tests/spec/PathTest.php @@ -141,8 +141,11 @@ public function testPathItemReference() $openapi = Reader::readFromYamlFile($file, \cebe\openapi\spec\OpenApi::class, false); $result = $openapi->validate(); - $this->assertEquals([], $openapi->getErrors(), print_r($openapi->getErrors(), true)); - $this->assertTrue($result); + $this->assertEquals([ + 'Invalid field: "X-EXTENSION"', + 'Invalid field: "xyz-extension"' + ], $openapi->getErrors(), print_r($openapi->getErrors(), true)); + $this->assertFalse($result); $this->assertInstanceOf(Paths::class, $openapi->paths); $this->assertInstanceOf(PathItem::class, $fooPath = $openapi->paths['/foo']); @@ -177,8 +180,11 @@ public function testPathItemReference() $openapi = Reader::readFromYamlFile($file, \cebe\openapi\spec\OpenApi::class, true); $result = $openapi->validate(); - $this->assertEquals([], $openapi->getErrors(), print_r($openapi->getErrors(), true)); - $this->assertTrue($result); + $this->assertEquals([ + 'Invalid field: "X-EXTENSION"', + 'Invalid field: "xyz-extension"' + ], $openapi->getErrors()); + $this->assertFalse($result); $this->assertInstanceOf(Paths::class, $openapi->paths); $this->assertInstanceOf(PathItem::class, $fooPath = $openapi->paths['/foo']); diff --git a/tests/spec/SchemaTest.php b/tests/spec/SchemaTest.php index 1600b3b..684c1d7 100644 --- a/tests/spec/SchemaTest.php +++ b/tests/spec/SchemaTest.php @@ -172,6 +172,7 @@ public function testDiscriminator() $result = $schema->validate(); $this->assertEquals([ + 'Invalid field: "map"', 'Discriminator is missing required property: propertyName' ], $schema->getErrors()); $this->assertFalse($result);