Add JSON schema validation feature.

This commit is contained in:
Greyscale 2021-12-07 00:45:25 +01:00
parent a385f4c134
commit c51d9b1cf6
No known key found for this signature in database
GPG key ID: D38CDF6CE08284DD
5 changed files with 145 additions and 1 deletions

View file

@ -62,6 +62,7 @@
"slim/slim": "^4.5",
"slim/twig-view": "^3.2",
"squizlabs/php_codesniffer": "3.*",
"swaggest/json-schema": "^0.12.39",
"symfony/translation": "^5.1",
"symfony/twig-bridge": "^5.1",
"symfony/yaml": "^5.1",

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Benzine\Annotations;
use Doctrine\Common\Annotations\Annotation\Required;
/**
* @Annotation
*
* @Target("METHOD")
*/
class JsonSchema
{
/**
* @Required
*/
public string $schema;
}

View file

@ -3,6 +3,8 @@
namespace Benzine;
use Benzine\Exceptions\JsonErrorHandler;
use Benzine\Middleware\JsonResponseExecTimeMiddleware;
use Benzine\Middleware\JsonValidationMiddleware;
use Benzine\ORM\Connection\Databases;
use Benzine\ORM\Laminator;
use Benzine\Redis\Redis;
@ -97,10 +99,14 @@ class App
// Configure Slim
$this->app = AppFactory::create();
$this->app->add(Slim\Views\TwigMiddleware::createFromContainer($this->app));
$this->app->addRoutingMiddleware();
$this->setupMiddlewares($container);
$this->app->add($container->get(JsonValidationMiddleware::class));
$this->app->addBodyParsingMiddleware();
$this->app->addRoutingMiddleware();
$this->app->add($container->get(JsonResponseExecTimeMiddleware::class));
// Determine if we're going to enable debug mode
$this->debugMode = $this->environmentService->get('DEBUG_MODE', 'off') == 'on';

View file

@ -0,0 +1,44 @@
<?php
namespace Benzine\Middleware;
use Benzine\Annotations\JsonSchema;
use Doctrine\Common\Annotations\AnnotationReader;
use Slim\Psr7\Factory\StreamFactory;
use Swaggest\JsonSchema\Exception;
use Swaggest\JsonSchema\Schema;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Request;
use Slim\Psr7\Response;
use Slim\Routing\RouteContext;
class JsonResponseExecTimeMiddleware implements MiddlewareInterface{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);
$response->getBody()->rewind();
$responseJson = json_decode($response->getBody()->getContents(), true);
if($responseJson === null){
return $response;
}
$responseJson['Exec'] = [
'TimeSeconds' => microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'],
'MemoryBytes' => memory_get_peak_usage(),
];
$replacementResponse = new Response();
$replacementResponse->getBody()->write(json_encode($responseJson, JSON_PRETTY_PRINT));
$replacementResponse = $replacementResponse->withHeader('Content-type', 'application/json');
$replacementResponse = $replacementResponse->withStatus($response->getStatusCode());
return $replacementResponse;
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace Benzine\Middleware;
use Benzine\Annotations\JsonSchema;
use Doctrine\Common\Annotations\AnnotationReader;
use Swaggest\JsonSchema\Exception;
use Swaggest\JsonSchema\Schema;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Request;
use Slim\Psr7\Response;
use Slim\Routing\RouteContext;
class JsonValidationMiddleware implements MiddlewareInterface{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// get the route out of the router...
$route = RouteContext::fromRequest($request)->getRoute();
// Load an annotation reader
$reader = new AnnotationReader();
// Break up our route into class & method
list ($class, $method) = explode(":", $route->getCallable());
// Create the reflection class for our class..
$rc = new \ReflectionClass($class);
// .. And snag the method
$method = $rc->getMethod($method);
// Try to read a json schema annotation..
$jsonSchemaAnnotation = $reader->getMethodAnnotation($method, JsonSchema::class);
// No annotation? Return early.
if(!($jsonSchemaAnnotation instanceof JsonSchema)){
return $handler->handle($request);
}
// Load the validator and the schema from disk
$schema = Schema::import(
json_decode(
file_get_contents(
$jsonSchemaAnnotation->schema
)
)
);
// Throw it through validation.. if it passes, continue
try{
// Validate it...
$schema->in(json_decode($request->getBody()->getContents()));
// And if we get here, we're golden.
return $handler->handle($request);
}catch (Exception $exception){
// Whelp, we've failed validation, build a failure message.
$response = new Response();
$content = json_encode([
'Status' => 'FAIL',
'Reason' => "Invalid JSON, doesn't match schema!",
'Error' => $exception->getMessage(),
], JSON_PRETTY_PRINT);
$response->getBody()->write($content);
$response = $response->withHeader('Content-type', 'application/json');
return $response->withStatus(400);
}
}
}