From 070a7668f242adf1b55758cbaa1d872be4431aa2 Mon Sep 17 00:00:00 2001 From: Pavel Batanov Date: Thu, 11 Jul 2019 12:55:13 +0300 Subject: [PATCH 1/2] Routed request validator --- src/PSR7/RoutedServerRequestValidator.php | 51 +++++++++++++ src/PSR7/ValidatorBuilder.php | 15 ++-- tests/PSR7/RoutedServerRequestTest.php | 91 +++++++++++++++++++++++ 3 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 src/PSR7/RoutedServerRequestValidator.php create mode 100644 tests/PSR7/RoutedServerRequestTest.php diff --git a/src/PSR7/RoutedServerRequestValidator.php b/src/PSR7/RoutedServerRequestValidator.php new file mode 100644 index 0000000..dc69071 --- /dev/null +++ b/src/PSR7/RoutedServerRequestValidator.php @@ -0,0 +1,51 @@ +openApi = $schema; + $finder = new SpecFinder($this->openApi); + $this->validator = new ValidatorChain( + new HeadersValidator($finder), + new CookiesValidator($finder), + new BodyValidator($finder), + new QueryArgumentsValidator($finder), + new PathValidator($finder), + new SecurityValidator($finder) + ); + } + + public function getSchema() : OpenApi + { + return $this->openApi; + } + + /** + * @throws ValidationFailed + */ + public function validate(OperationAddress $opAddr, ServerRequestInterface $serverRequest) : void + { + $this->validator->validate($opAddr, $serverRequest); + } +} diff --git a/src/PSR7/ValidatorBuilder.php b/src/PSR7/ValidatorBuilder.php index 81cbaa4..24af44a 100644 --- a/src/PSR7/ValidatorBuilder.php +++ b/src/PSR7/ValidatorBuilder.php @@ -112,6 +112,16 @@ public function getServerRequestValidator() : ServerRequestValidator return new ServerRequestValidator($schema); } + public function getResponseValidator() : ResponseValidator + { + return new ResponseValidator($this->getOrCreateSchema()); + } + + public function getRoutedRequestValidator() : RoutedServerRequestValidator + { + return new RoutedServerRequestValidator($this->getOrCreateSchema()); + } + protected function getOrCreateSchema() : OpenApi { // Make cache dependency optional for end user @@ -151,9 +161,4 @@ protected function getCacheKey() : string return $this->factory->getCacheKey(); } - - public function getResponseValidator() : ResponseValidator - { - return new ResponseValidator($this->getOrCreateSchema()); - } } diff --git a/tests/PSR7/RoutedServerRequestTest.php b/tests/PSR7/RoutedServerRequestTest.php new file mode 100644 index 0000000..8086654 --- /dev/null +++ b/tests/PSR7/RoutedServerRequestTest.php @@ -0,0 +1,91 @@ +makeGoodServerRequest('/path1', 'get'); + + $validator = (new ValidatorBuilder())->fromYamlFile($this->apiSpecFile)->getRoutedRequestValidator(); + $validator->validate(new OperationAddress('/path1', 'get'), $request); + $this->addToAssertionCount(1); + } + + public function testItValidatesBodyGreen() : void + { + $body = ['name' => 'Alex']; + $request = $this->makeGoodServerRequest('/request-body', 'post') + ->withBody(stream_for(json_encode($body))); + + $validator = (new ValidatorBuilder())->fromYamlFile($this->apiSpecFile)->getRoutedRequestValidator(); + $validator->validate(new OperationAddress('/request-body', 'post'), $request); + $this->addToAssertionCount(1); + } + + public function testItValidatesBodyHasInvalidPayloadRed() : void + { + $addr = new OperationAddress('/request-body', 'post'); + $body = ['name' => 1000]; + $request = $this->makeGoodServerRequest($addr->path(), $addr->method()) + ->withBody(stream_for(json_encode($body))); + + $this->expectException(InvalidBody::class); + $this->expectExceptionMessage( + 'Body does not match schema for content-type "application/json" for Request [post /request-body]' + ); + + $validator = (new ValidatorBuilder())->fromYamlFile($this->apiSpecFile)->getRoutedRequestValidator(); + $validator->validate(new OperationAddress('/request-body', 'post'), $request); + } + + public function testItValidatesBodyHasUnexpectedTypeRed() : void + { + $addr = new OperationAddress('/request-body', 'post'); + $request = $this->makeGoodServerRequest($addr->path(), $addr->method()) + ->withoutHeader('Content-Type') + ->withHeader('Content-Type', 'unexpected/content'); + + $this->expectException(InvalidHeaders::class); + $this->expectExceptionMessage( + 'Content-Type "unexpected/content" is not expected for Request [post /request-body]' + ); + + $validator = (new ValidatorBuilder())->fromYamlFile($this->apiSpecFile)->getRoutedRequestValidator(); + $validator->validate(new OperationAddress('/request-body', 'post'), $request); + } + + public function testItValidatesMessageWrongHeaderValueRed() : void + { + $addr = new OperationAddress('/path1', 'get'); + $request = $this->makeGoodServerRequest($addr->path(), $addr->method())->withHeader('Header-A', 'wrong value'); + + $this->expectException(InvalidHeaders::class); + $this->expectExceptionMessage('Value "wrong value" for header "Header-A" is invalid for Request [get /path1]'); + + $validator = (new ValidatorBuilder())->fromYamlFile($this->apiSpecFile)->getRoutedRequestValidator(); + $validator->validate(new OperationAddress('/path1', 'get'), $request); + } + + public function testItValidatesMessageMissedHeaderRed() : void + { + $addr = new OperationAddress('/path1', 'get'); + $request = $this->makeGoodServerRequest($addr->path(), $addr->method())->withoutHeader('Header-A'); + + $this->expectException(InvalidHeaders::class); + $this->expectExceptionMessage('Missing required header "Header-A" for Request [get /path1]'); + + $validator = (new ValidatorBuilder())->fromYamlFile($this->apiSpecFile)->getRoutedRequestValidator(); + $validator->validate(new OperationAddress('/path1', 'get'), $request); + } +} From 5e033d509120416dc4267f86acdad49c367611c8 Mon Sep 17 00:00:00 2001 From: Pavel Batanov Date: Tue, 16 Jul 2019 17:29:15 +0300 Subject: [PATCH 2/2] Add README note --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 55364e6..a00673d 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,23 @@ $validator = (new \OpenAPIValidation\PSR7\ValidatorBuilder)->fromJsonFile($jsonF $schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand $validator = (new \OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getServerRequestValidator(); -$validator->validate($request); +$match = $validator->validate($request); ``` +As a result you would get and `OperationAddress $match` which has matched the given request. If you already know +the operation which should match your request (i.e you have routing in your project), you can use +`RouterRequestValidator` + +```php +$address = new \OpenAPIValidation\PSR7\OperationAddress('/some/operation', 'post'); + +$validator = (new \OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getRoutedRequestValidator(); + +$validator->validate($address, $request); +``` + +This would simplify validation a lot and give you more performance. + ### Response Message Validation of `\Psr\Http\Message\ResponseInterface` is a bit more complicated . Because you need not only YAML file and Response itself, but also you need