Skip to content
This repository has been archived by the owner on Jul 19, 2024. It is now read-only.

Commit

Permalink
refactor event validation and now validate events on dispatch (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewgoslett authored Jul 17, 2017
1 parent 5a2d299 commit bfdb14e
Show file tree
Hide file tree
Showing 11 changed files with 869 additions and 47 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,9 @@ Your validator must implement the following methods.
```php
/**
* @param EventInterface $event
* @return bool
* @return ValidationResult
*/
public function validates(EventInterface $event);
public function validate(EventInterface $event);
```

## Attribute Injectors
Expand Down Expand Up @@ -413,8 +413,9 @@ $manager->setListenExprFailHandler(function (\Superbalist\EventPubSub\EventInter
});

// hook into validation failures
$manager->setValidationFailHandler(function (\Superbalist\EventPubSub\EventInterface $event, \Superbalist\EventPubSub\EventValidatorInterface $validator) {
$manager->setValidationFailHandler(function (\Superbalist\EventPubSub\ValidationResult $result) {
// the event failed validation
var_dump($result->errors());
});
```

Expand Down
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 4.0.0 - 2017-07-17

* EventValidatorInterface->validates() renamed to ->validate()
* EventValidatorInterface->validate() now returns a ValidationResult instance instead of bool
* The validation fail handler callback now receives a ValidationResult instead of the event and a validator
* Events are now validated on dispatch, and will throw a ValidationException if throwValidationExceptionsOnDispatch is true (defaults to true)
* Added new throwValidationExceptionsOnDispatch(bool) method to EventManager to suppress validation exceptions on dispatch

## 3.0.1 - 2017-07-17

* Add support for translate, listen expr & validation failure callbacks
Expand Down
82 changes: 72 additions & 10 deletions src/EventManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ class EventManager
*/
protected $validationFailHandler;

/**
* @var bool
*/
protected $throwValidationExceptionsOnDispatch = true;

/**
* @param PubSubAdapterInterface $adapter
* @param MessageTranslatorInterface $translator
Expand Down Expand Up @@ -159,7 +164,7 @@ public function getListenExprFailHandler()
}

/**
* Set the handler which is called when an event is received but fails validation.
* Set the handler which is called when an event is dispatched or received but fails validation.
*
* @param callable $handler
*/
Expand All @@ -169,7 +174,7 @@ public function setValidationFailHandler(callable $handler)
}

/**
* Return the handler which is called when an event is received but fails validation.
* Return the handler which is called when an event is dispatched or received but fails validation.
*
* @return callable|null
*/
Expand All @@ -178,6 +183,20 @@ public function getValidationFailHandler()
return $this->validationFailHandler;
}

/**
* @param bool|null $bool
*
* @return mixed
*/
public function throwValidationExceptionsOnDispatch($bool = null)
{
if ($bool === null) {
return $this->throwValidationExceptionsOnDispatch;
} else {
$this->throwValidationExceptionsOnDispatch = $bool;
}
}

/**
* Listen for an event.
*
Expand Down Expand Up @@ -206,13 +225,19 @@ public function handleSubscribeCallback($message, $expr, callable $handler)
// we were able to translate the message into an event
if ($event->matches($expr)) {
// the event matches the listen expression
if ($this->validator === null || $this->validator->validates($event)) {
// event passed validation
if ($this->validator === null) {
// nothing to validate
call_user_func($handler, $event);
} else {
// pass to validation fail handler?
if ($this->validationFailHandler) {
call_user_func($this->validationFailHandler, $event, $this->validator);
$result = $this->validator->validate($event);
if ($result->passes()) {
// event validates!
call_user_func($handler, $event);
} else {
// pass to validation fail handler?
if ($this->validationFailHandler) {
call_user_func($this->validationFailHandler, $result);
}
}
}
} else {
Expand Down Expand Up @@ -271,10 +296,26 @@ protected function prepEventForDispatch(EventInterface $event)
*
* @param string $channel
* @param EventInterface $event
*
* @throws ValidationException
*/
public function dispatch($channel, EventInterface $event)
{
$e = $this->prepEventForDispatch($event);
if ($this->validator) {
$result = $this->validator->validate($event);
if ($result->fails()) {
// pass to validation fail handler?
if ($this->validationFailHandler) {
call_user_func($this->validationFailHandler, $result);
}

if ($this->throwValidationExceptionsOnDispatch) {
throw new ValidationException($result);
}
}
}

$this->adapter->publish($channel, $e->toMessage());
}

Expand All @@ -283,13 +324,34 @@ public function dispatch($channel, EventInterface $event)
*
* @param string $channel
* @param array $events
*
* @throws ValidationException
*/
public function dispatchBatch($channel, array $events)
{
$messages = array_map(function (EventInterface $event) {
$messages = [];

foreach ($events as $event) {
/** @var EventInterface $event */
$event = $this->prepEventForDispatch($event);
return $event->toMessage();
}, $events);

if ($this->validator) {
$result = $this->validator->validate($event);
if ($result->fails()) {
// pass to validation fail handler?
if ($this->validationFailHandler) {
call_user_func($this->validationFailHandler, $result);
}

if ($this->throwValidationExceptionsOnDispatch) {
throw new ValidationException($result);
}
}
}

$messages[] = $event->toMessage();
}

$this->adapter->publishBatch($channel, $messages);
}
}
4 changes: 2 additions & 2 deletions src/EventValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface EventValidatorInterface
/**
* @param EventInterface $event
*
* @return bool
* @return ValidationResult
*/
public function validates(EventInterface $event);
public function validate(EventInterface $event);
}
39 changes: 39 additions & 0 deletions src/ValidationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Superbalist\EventPubSub;

use Exception;
use Throwable;

class ValidationException extends Exception
{
/**
* @var ValidationResult
*/
protected $validationResult;

/**
* @param ValidationResult $validationResult
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(
ValidationResult $validationResult,
$message = 'The event failed validation.',
$code = 0,
Throwable $previous = null
) {
parent::__construct($message, $code, $previous);

$this->validationResult = $validationResult;
}

/**
* @return ValidationResult
*/
public function getValidationResult()
{
return $this->validationResult;
}
}
88 changes: 88 additions & 0 deletions src/ValidationResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Superbalist\EventPubSub;

class ValidationResult
{
/**
* @var EventValidatorInterface
*/
protected $validator;

/**
* @var EventInterface
*/
protected $event;

/**
* @var bool
*/
protected $passes;

/**
* @var array
*/
protected $errors;

/**
* @param EventValidatorInterface $validator
* @param EventInterface $event
* @param bool $passes
* @param array $errors
*/
public function __construct(EventValidatorInterface $validator, EventInterface $event, $passes, array $errors = [])
{
$this->validator = $validator;
$this->event = $event;
$this->passes = $passes;
$this->errors = $errors;
}

/**
* @return EventValidatorInterface
*/
public function getValidator()
{
return $this->validator;
}

/**
* @return EventInterface
*/
public function getEvent()
{
return $this->event;
}

/**
* @return bool
*/
public function passes()
{
return $this->passes;
}

/**
* @return bool
*/
public function fails()
{
return !$this->passes;
}

/**
* @return array
*/
public function errors()
{
return $this->errors;
}

/**
* @return array
*/
public function getErrors()
{
return $this->errors();
}
}
29 changes: 20 additions & 9 deletions src/Validators/JSONSchemaEventValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
namespace Superbalist\EventPubSub\Validators;

use League\JsonGuard\Dereferencer;
use League\JsonGuard\ValidationError;
use League\JsonGuard\Validator;
use Superbalist\EventPubSub\EventInterface;
use Superbalist\EventPubSub\Events\SchemaEvent;
use Superbalist\EventPubSub\EventValidatorInterface;
use Superbalist\EventPubSub\ValidationResult;

class JSONSchemaEventValidator implements EventValidatorInterface
{
Expand All @@ -26,34 +28,43 @@ public function __construct(Dereferencer $dereferencer)
/**
* @param EventInterface $event
*
* @return bool
* @return ValidationResult
*/
public function validates(EventInterface $event)
public function validate(EventInterface $event)
{
$schema = $this->getEventSchema($event);
if ($schema === null) {
return true;
return new ValidationResult($this, $event, true);
}

$schemaValidator = $this->makeSchemaValidator($event, $schema);
if ($schemaValidator->passes()) {
return new ValidationResult($this, $event, true);
} else {
$errors = [];
foreach ($schemaValidator->errors() as $error) {
/* @var ValidationError $error */
$errors[] = $error->getMessage();
}
return new ValidationResult($this, $event, false, $errors);
}
return $this->isValidAgainstSchema($event, $schema);
}

/**
* @param EventInterface $event
* @param object $schema
*
* @return bool
* @return Validator
*/
public function isValidAgainstSchema(EventInterface $event, $schema)
public function makeSchemaValidator(EventInterface $event, $schema)
{
// we can't validate on an array, only an object
// so we need to convert the event payload to an object
$payload = $event->toMessage();
$payload = json_encode($payload); // back to json
$payload = json_decode($payload); // from json to an object

$validator = new Validator($payload, $schema);

return $validator->passes();
return new Validator($payload, $schema);
}

/**
Expand Down
Loading

0 comments on commit bfdb14e

Please sign in to comment.