Skip to content

Commit

Permalink
Merge pull request #5 from ytake/feature/router-query-param
Browse files Browse the repository at this point in the history
added request validation attributes
  • Loading branch information
ytake authored Jan 15, 2018
2 parents a3e751a + 68cad2c commit 85a28e3
Show file tree
Hide file tree
Showing 22 changed files with 448 additions and 63 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ Http Application / Microframework for HHVM/Hack
[Skeleton](https://github.com/ytake/nazg-skeleton)

```bash
$ hhvm -d hhvm.php7.all=1 -d hhvm.jit=0 $(which composer) create-project nazg/skeleton nazg-app --prefer-dist -s dev
$ hhvm -d xdebug.enable=0 -d hhvm.jit=0 -d hhvm.php7.all=1 -d hhvm.hack.lang.auto_typecheck=0 $(which composer) update
```

6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"ext-asio": "*",
"hhvm/hhvm-autoload": "^1.5",
"hhvm/hsl": "^1.0.0",
"hhvm/type-assert": "^3.2.0",
"hack-psr/psr7-http-message-hhi": "^1.0.0",
"ytake/hh-container": "^0.5",
"ytake/hh-config-aggregator": "^0.1.1",
Expand All @@ -40,7 +41,10 @@
"autoload": {
"psr-4": {
"Nazg\\": "src/"
}
},
"files": [
"src/types.php"
]
},
"autoload-dev": {
"classmap": [
Expand Down
58 changes: 48 additions & 10 deletions src/Foundation/Application.php
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
<?hh // strict
<?hh

namespace Nazg\Foundation;

use Facebook\HackRouter\BaseRouter;
use Ytake\Heredity\Heredity;
use Ytake\Heredity\MiddlewareStack;
use Ytake\Heredity\PsrContainerResolver;
use Nazg\Http\HttpMethod;
use Nazg\RequestHandler\FallbackHandler;
use Nazg\Foundation\Middleware\Dispatcher;
use Nazg\Foundation\Dependency\DependencyInterface;
use Nazg\Response\Response;
use Nazg\Routing\HttpMethod;
use Interop\Http\Server\RequestHandlerInterface;
use Interop\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Container\ContainerInterface;
use Zend\Diactoros\Response\SapiEmitter;
use Zend\Diactoros\Response\EmitterInterface;

type TMiddlewareClass = classname<MiddlewareInterface>;
type TServiceModule = classname<\Ytake\HHContainer\ServiceModule>;
Expand All @@ -23,6 +24,8 @@ class Application {

protected ImmVector<TMiddlewareClass> $im = ImmVector{};

protected ?RequestHandlerInterface $requestHandler;

public function __construct(
protected DependencyInterface $dependency
) {}
Expand All @@ -38,10 +41,34 @@ public function run(
get_class($router),
BaseRouter::class
);
list($middleware, $path) = $router->routePsr7Request($serverRequest);
list($middleware, $attributes) = $router->routePsr7Request($serverRequest);
if ($attributes->count()) {
foreach($attributes as $key => $attribute) {
$serverRequest = $serverRequest->withAttribute($key, $attribute);
}
}
$heredity = $this->middlewareProcessor($middleware, $container);
$response = new Response($heredity->process($serverRequest, new FallbackHandler()));
$response->send();
$this->send(
$heredity->handle(
$this->marshalAttributes($serverRequest, $attributes)
)
);
}

protected function marshalAttributes(
ServerRequestInterface $request,
ImmMap<string, string> $attributes
): ServerRequestInterface {
if ($attributes->count()) {
foreach($attributes as $key => $attribute) {
$request = $request->withAttribute($key, $attribute);
}
}
return $request;
}

public function setRequestHandler(RequestHandlerInterface $handler): void {
$this->requestHandler = $handler;
}

public function setApplicationConfig(array<mixed, mixed> $config): void {
Expand Down Expand Up @@ -81,14 +108,25 @@ private function registerMiddlewares(mixed $config): void {
protected function middlewareProcessor(
TMiddlewareClass $middleware,
ContainerInterface $container
): MiddlewareInterface {
): RequestHandlerInterface {
$appMiddleware = $this->im->concat($this->middleware())
|>$$->concat(Set{$middleware})->toArray();
return new Heredity(
$dispatcher = new Dispatcher(
new MiddlewareStack(
$appMiddleware,
new PsrContainerResolver($container)
),
);
$this->requestHandler ?: new FallbackHandler()
);
$dispatcher->setContainer($container);
return $dispatcher;
}

protected function send(ResponseInterface $response): void {
$this->emitter()->emit($response);
}

protected function emitter(): EmitterInterface {
return new SapiEmitter();
}
}
54 changes: 54 additions & 0 deletions src/Foundation/Middleware/Dispatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?hh // strict

namespace Nazg\Foundation\Middleware;

use ReflectionMethod;
use Ytake\Heredity\Heredity;
use Nazg\Foundation\Validation\Attribute;
use Nazg\Foundation\Validation\Validator;
use Nazg\Foundation\Validation\ValidatorFactory;
use Interop\Http\Server\MiddlewareInterface;
use Interop\Http\Server\RequestHandlerInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

enum InterceptorMethod: string {
Process = 'process';
}

class Dispatcher extends Heredity {

protected int $validatorIndex = 0;
protected ?ContainerInterface $container;

<<__Override>>
protected function processor(
MiddlewareInterface $middleware,
ServerRequestInterface $request
): ResponseInterface {
$this->validateInterceptor($middleware, $request);
return $middleware->process($request, $this);
}

protected function validateInterceptor(
MiddlewareInterface $middleware,
ServerRequestInterface $request
): void {
$rm = new ReflectionMethod($middleware, InterceptorMethod::Process);
$attribute = $rm->getAttribute(Attribute::Named);
if (is_array($attribute)) {
if(array_key_exists($this->validatorIndex, $attribute)) {
$validator = $this->container?->get((string)$attribute[$this->validatorIndex]);
if($validator instanceof Validator) {
$factory = new ValidatorFactory($validator, $request);
$factory->validator()->validate();
}
}
}
}

public function setContainer(ContainerInterface $container): void {
$this->container = $container;
}
}
21 changes: 21 additions & 0 deletions src/Foundation/Validation/ValidationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?hh // strict

namespace Nazg\Foundation\Validation;

use Exception;

class ValidationException extends Exception {

protected int $status = 400;

protected Validator $validator;

public function __construct(Validator $validator) {
parent::__construct('The given data was invalid.');
$this->validator = $validator;
}

public function errors(): array<string> {
return $this->validator->errors()->toArray();
}
}
55 changes: 55 additions & 0 deletions src/Foundation/Validation/Validator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?hh // strict

namespace Nazg\Foundation\Validation;

use Psr\Http\Message\ServerRequestInterface;
use Facebook\TypeAssert;

enum Attribute: string as string {
Named = 'RequestValidation';
}

<<__ConsistentConstruct>>
abstract class Validator {

protected ?ServerRequestInterface $request;

protected Vector<string> $messages = Vector{};

protected Vector<string> $validateMethods = Vector{};

protected bool $shouldThrowException = false;

// disabled type assert for request parameters
protected bool $skipValidateStructure = true;

public function validateRequest(ServerRequestInterface $request): void {
$this->request = $request;
}

public function validate(): bool {
if (!is_null($this->request)) {
$this->assertStructure();
}
if($this->errors()->count()) {
if ($this->shouldThrowException) {
throw new ValidationException($this);
}
return false;
}
return true;
}

<<__Memoize>>
public function errors(): ImmVector<string> {
return $this->assertValidateResult()->immutable();
}

protected function assertStructure(): void {
if(!$this->skipValidateStructure) {
// here
}
}

abstract protected function assertValidateResult(): Vector<string>;
}
19 changes: 19 additions & 0 deletions src/Foundation/Validation/ValidatorFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?hh // strict

namespace Nazg\Foundation\Validation;

use Psr\Http\Message\ServerRequestInterface;

class ValidatorFactory {

public function __construct(
protected Validator $validatorName,
protected ServerRequestInterface $request
) {}

public function validator(): Validator {
$validator = $this->validatorName;
$validator->validateRequest($this->request);
return $validator;
}
}
2 changes: 1 addition & 1 deletion src/Middleware/SimpleCorsMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Nazg\Middleware;

use Nazg\Routing\HttpMethod;
use Nazg\Http\HttpMethod;
use Psr\Log\LoggerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand Down
20 changes: 0 additions & 20 deletions src/Response/Response.php

This file was deleted.

1 change: 1 addition & 0 deletions src/Routing/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Nazg\Routing;

use Nazg\Http\HttpMethod;
use Facebook\HackRouter\BaseRouter;
use Facebook\HackRouter\HttpMethod as HackRouterHttpMethod;
use Interop\Http\Server\MiddlewareInterface;
Expand Down
4 changes: 2 additions & 2 deletions src/Routing/HttpMethod.php → src/types.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?hh //strict
<?hh // strict

namespace Nazg\Routing;
namespace Nazg\Http;

enum HttpMethod: string {
HEAD = 'HEAD';
Expand Down
18 changes: 18 additions & 0 deletions tests/Action/ParameterAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?hh

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Interop\Http\Server\MiddlewareInterface;
use Interop\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\JsonResponse;

final class ParameterAction implements MiddlewareInterface {
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
): ResponseInterface {
return new JsonResponse(
$request->getAttributes() + $request->getQueryParams()
);
}
}
19 changes: 19 additions & 0 deletions tests/Action/ValidateAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?hh

use Nazg\Foundation\Validation\Attribute;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Interop\Http\Server\MiddlewareInterface;
use Interop\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\JsonResponse;

final class ValidateAction implements MiddlewareInterface {

<<RequestValidation(\MockValidateActionFaild::class)>>
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
): ResponseInterface {
return new JsonResponse([]);
}
}
Loading

0 comments on commit 85a28e3

Please sign in to comment.