Skip to content

Commit

Permalink
Merge pull request #43 from scaytrase/feature/routed-request-validator
Browse files Browse the repository at this point in the history
Routed request validator
  • Loading branch information
lezhnev74 authored Jul 16, 2019
2 parents 6d61b91 + 5e033d5 commit 04b93fc
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 6 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 51 additions & 0 deletions src/PSR7/RoutedServerRequestValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace OpenAPIValidation\PSR7;

use cebe\openapi\spec\OpenApi;
use OpenAPIValidation\PSR7\Exception\ValidationFailed;
use OpenAPIValidation\PSR7\Validators\BodyValidator;
use OpenAPIValidation\PSR7\Validators\CookiesValidator;
use OpenAPIValidation\PSR7\Validators\HeadersValidator;
use OpenAPIValidation\PSR7\Validators\PathValidator;
use OpenAPIValidation\PSR7\Validators\QueryArgumentsValidator;
use OpenAPIValidation\PSR7\Validators\SecurityValidator;
use OpenAPIValidation\PSR7\Validators\ValidatorChain;
use Psr\Http\Message\ServerRequestInterface;

class RoutedServerRequestValidator implements ReusableSchema
{
/** @var OpenApi */
protected $openApi;
/** @var MessageValidator */
protected $validator;

public function __construct(OpenApi $schema)
{
$this->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);
}
}
15 changes: 10 additions & 5 deletions src/PSR7/ValidatorBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -151,9 +161,4 @@ protected function getCacheKey() : string

return $this->factory->getCacheKey();
}

public function getResponseValidator() : ResponseValidator
{
return new ResponseValidator($this->getOrCreateSchema());
}
}
91 changes: 91 additions & 0 deletions tests/PSR7/RoutedServerRequestTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace OpenAPIValidationTests\PSR7;

use OpenAPIValidation\PSR7\Exception\Validation\InvalidBody;
use OpenAPIValidation\PSR7\Exception\Validation\InvalidHeaders;
use OpenAPIValidation\PSR7\OperationAddress;
use OpenAPIValidation\PSR7\ValidatorBuilder;
use function GuzzleHttp\Psr7\stream_for;
use function json_encode;

final class RoutedServerRequestTest extends BaseValidatorTest
{
public function testItValidatesMessageGreen() : void
{
$request = $this->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);
}
}

0 comments on commit 04b93fc

Please sign in to comment.