-
Notifications
You must be signed in to change notification settings - Fork 56
split a simpler json-schema validator from the route-based-validator #46
Changes from 1 commit
388071a
4358d38
5b09668
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -913,6 +913,27 @@ $middlewares = [ | |
]; | ||
``` | ||
|
||
### JsonValidator | ||
|
||
Uses [justinrainbow/json-schema](https://github.com/justinrainbow/json-schema) to validate an `application/json` request body with a JSON schema: | ||
|
||
```php | ||
use Psr7Middlewares\Middleware; | ||
|
||
$middlewares = [ | ||
|
||
// Transform `application/json` into an object, which is a requirement of `justinrainbow/json-schema`. | ||
Middleware::payload([ | ||
'forceArray' => false, | ||
]), | ||
|
||
// Specify a JSON file (publicly-accessible in this example), or a JSON string decoded into object-notation. | ||
Middleware::jsonValidator((object) [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you're pushing quite a bit of implementation detail to the middleware definition here... I'd prefer to just push a reference to a file (either SplFileObject or a string to the path), rather than deal with the specifics of how the chosen Schema Validator needs to receive the schema. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great point. the implementation uses justinrainbow/json-schema, which can accept a file ref or a decoded schema. so your validator factory could create a schema inline, without using any files or file ops. do we really want to enforce file only? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, i could create a couple of named factories, one for a decoded object, and another that accepts SplFileObject (great suggestion). |
||
'$ref' => WEB_ROOT . '/json-schema/en.v1.users.json', | ||
]) | ||
]; | ||
``` | ||
|
||
### JsonSchema | ||
|
||
Uses [justinrainbow/json-schema](https://github.com/justinrainbow/json-schema) to validate an `application/json` request body using route-matched JSON schemas: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<?php | ||
|
||
namespace Psr7Middlewares\Middleware; | ||
|
||
use JsonSchema\Validator; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
class JsonValidator | ||
{ | ||
/** @var \stdClass */ | ||
private $schema; | ||
|
||
/** | ||
* JsonSchema constructor. | ||
* | ||
* @param \stdClass $schema A JSON-decoded object-representation of the schema. | ||
*/ | ||
public function __construct(\stdClass $schema) | ||
{ | ||
$this->schema = $schema; | ||
} | ||
|
||
/** | ||
* Execute the middleware. | ||
* | ||
* @param ServerRequestInterface $request | ||
* @param ResponseInterface $response | ||
* @param callable $next | ||
* | ||
* @return ResponseInterface | ||
* @throws \RuntimeException | ||
* @throws \InvalidArgumentException | ||
* @throws \JsonSchema\Exception\ExceptionInterface | ||
*/ | ||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) | ||
{ | ||
$value = $request->getParsedBody(); | ||
if (!is_object($value)) { | ||
return $this->invalidateResponse( | ||
$response, | ||
sprintf('Parsed body must be an object. Type %s is invalid.', gettype($value)) | ||
); | ||
} | ||
|
||
$validator = new Validator(); | ||
$validator->check($value, $this->schema); | ||
|
||
if (!$validator->isValid()) { | ||
return $this->invalidateResponse( | ||
$response, | ||
'Unprocessable Entity', | ||
[ | ||
'Content-Type' => 'application/json', | ||
], | ||
json_encode($validator->getErrors(), JSON_UNESCAPED_SLASHES) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're assuming the API will be providing a JSON response here, which is not necessarily true for all cases, even if we're in the business of receiving JSON. I'd be very surprised as a developer if a middleware layer suddenly decided to switch the content-type around. Think for example about people using JSON-API, which specifies a content type of @oscarotero Does the project have a "best practice" way of dealing with generating responses to match the Accept headers provided by the user? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the response generation can be configured in a similar way than errorHandler or shutdown, providing a callable handler: $errorResponse = function ($request, $response) {
$validator = JsonValidator::getValidator($request);
$response = $response->withHeader('Content-Type', 'application/json');
$response->getBody()->write(json_encode($validator->getErrors(), JSON_UNESCAPED_SLASHES));
};
$middlewares[] = Middlewares::JsonValidator()->errorHandler($errorResponse); And if no custom errorHandler is provided, use the default. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok thanks, i'll try this out. great suggestions @sbol-coolblue and @oscarotero . |
||
); | ||
} | ||
|
||
return $next($request, $response); | ||
} | ||
|
||
/** | ||
* @param ResponseInterface $response | ||
* @param string $reason | ||
* @param string[] $headers | ||
* @param string|null $body | ||
* | ||
* @return ResponseInterface | ||
* @throws \RuntimeException | ||
* @throws \InvalidArgumentException | ||
*/ | ||
private function invalidateResponse(ResponseInterface $response, $reason, array $headers = [], $body = null) | ||
{ | ||
$response = $response->withStatus(422, $reason); | ||
|
||
foreach ($headers as $name => $value) { | ||
$response = $response->withHeader($name, $value); | ||
} | ||
|
||
if ($body !== null) { | ||
$stream = $response->getBody(); | ||
$stream->write($body); | ||
} | ||
|
||
return $response; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment does not match the method signature... the case of specifying a file is not supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the underlying lib accepts the ref as an object, e.g.