From 6f80a8747dfea1f59892a34eaf9eb13faa8ec4c4 Mon Sep 17 00:00:00 2001 From: Davide Pastore Date: Fri, 7 Sep 2018 00:06:12 +0200 Subject: [PATCH 1/9] Attempt to fix the validation of an array of objects --- src/Validation.php | 49 ++++++++++++++++++++++++++++++++-------- tests/ValidationTest.php | 44 ++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/Validation.php b/src/Validation.php index 3a31572..f05479d 100644 --- a/src/Validation.php +++ b/src/Validation.php @@ -3,6 +3,7 @@ namespace DavidePastore\Slim\Validation; use Respect\Validation\Exceptions\NestedValidationException; +use Respect\Validation\Validator; /** * Validation for Slim. @@ -66,6 +67,13 @@ class Validation */ protected $translator_name = 'translator'; + /** + * Is the validators an instance of Validator? + * + * @var boolean + */ + protected $isValidator = false; + /** * Create new Validator service provider. * @@ -78,6 +86,9 @@ public function __construct($validators = null, $translator = null, $options = [ // Set the validators if (is_array($validators) || $validators instanceof \ArrayAccess) { $this->validators = $validators; + } elseif ($validators instanceof Validator) { + $this->validators = $validators; + $this->isValidator = true; } elseif (is_null($validators)) { $this->validators = []; } @@ -99,7 +110,11 @@ public function __invoke($request, $response, $next) $this->errors = []; $params = $request->getParams(); $params = array_merge((array) $request->getAttribute('routeInfo')[2], $params); - $this->validate($params, $this->validators); + if ($this->isValidator) { + $this->validateParam($params, $this->validators); + } else { + $this->validate($params, $this->validators); + } $request = $request->withAttribute($this->errors_name, $this->getErrors()); $request = $request->withAttribute($this->has_errors_name, $this->hasErrors()); @@ -126,14 +141,7 @@ private function validate($params = [], $validators = [], $actualKeys = []) if (is_array($validator)) { $this->validate($params, $validator, $actualKeys); } else { - try { - $validator->assert($param); - } catch (NestedValidationException $exception) { - if ($this->translator) { - $exception->setParam('translator', $this->translator); - } - $this->errors[implode('.', $actualKeys)] = $exception->getMessages(); - } + $this->validateParam($param, $validator, $actualKeys); } //Remove the key added in this foreach @@ -141,6 +149,29 @@ private function validate($params = [], $validators = [], $actualKeys = []) } } + /** + * Validate a param. + * @param any $param The parameter to validate. + * @param any $validator The validator to use to validate the given parameter. + * @param array $actualKeys An array with the position of the parameter. + */ + private function validateParam($param, $validator, $actualKeys = []) { + try { + $validator->assert($param); + } catch (NestedValidationException $exception) { + if ($this->translator) { + $exception->setParam('translator', $this->translator); + } + + $messages = $exception->getMessages(); + if (empty($actualKeys)) { + $this->errors = $messages; + } else { + $this->errors[implode('.', $actualKeys)] = $messages; + } + } + } + /** * Get the nested parameter value. * diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index cb3e7f3..629e02d 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -271,6 +271,50 @@ function ($message) { 'username' => 'jsonusername', ), ), + + //JSON validation with array without errors + array( + v::each( + v::keySet( + v::key("id", v::intVal()), + v::key("key", v::stringType()) + ) + ), + null, + false, + array(), + 'JSON', + array( + array( + 'id' => 1234, + 'key' => 'value' + ), + ), + ), + + //JSON validation with array with errors + array( + v::each( + v::keySet( + v::key("id", v::intVal()), + v::key("key", v::stringType()) + ) + ), + null, + true, + array( + 'Must have keys { "id", "key" }' + ), + 'JSON', + array( + array( + 'id' => 1234, + 'key' => 'value', + 'unwanted' => 'value' + ), + ), + ), + //Complex JSON validation without errors array( array( From 2a2ae95eb2f530c719f71e9dca4f83d3d675e844 Mon Sep 17 00:00:00 2001 From: Davide Pastore Date: Fri, 7 Sep 2018 00:09:54 +0200 Subject: [PATCH 2/9] Fix from styleci --- src/Validation.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Validation.php b/src/Validation.php index f05479d..fdd8461 100644 --- a/src/Validation.php +++ b/src/Validation.php @@ -153,9 +153,10 @@ private function validate($params = [], $validators = [], $actualKeys = []) * Validate a param. * @param any $param The parameter to validate. * @param any $validator The validator to use to validate the given parameter. - * @param array $actualKeys An array with the position of the parameter. + * @param array $actualKeys An array with the position of the parameter. */ - private function validateParam($param, $validator, $actualKeys = []) { + private function validateParam($param, $validator, $actualKeys = []) + { try { $validator->assert($param); } catch (NestedValidationException $exception) { From 2f571f53c48df01e8b6c921d2961d38f14c68b38 Mon Sep 17 00:00:00 2001 From: Davide Pastore Date: Sun, 25 Nov 2018 16:57:03 +0100 Subject: [PATCH 3/9] Add some tests with nested array --- tests/ValidationTest.php | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index 629e02d..448c548 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -315,6 +315,52 @@ function ($message) { ), ), + //Complex JSON validation with array without errors + array( + v::each( + v::keySet( + v::key("nested", v::keySet( + v::key("id", v::intVal()) + )) + ) + ), + null, + false, + array(), + 'JSON', + array( + array( + 'nested' => array( + 'id' => 1234 + ), + ), + ), + ), + + //Complex JSON validation with array with errors + array( + v::each( + v::keySet( + v::key("nested", v::keySet( + v::key("key", v::stringType()) + )) + ) + ), + null, + true, + array( + 'key must be a string' + ), + 'JSON', + array( + array( + 'nested' => array( + 'key' => 1234 + ), + ), + ), + ), + //Complex JSON validation without errors array( array( From de6c0d34e3346a6921620051b318ab87121b2fbe Mon Sep 17 00:00:00 2001 From: Davide Pastore Date: Sun, 25 Nov 2018 18:00:37 +0100 Subject: [PATCH 4/9] Add empty array test --- tests/ValidationTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index 448c548..d31cd7e 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -315,6 +315,22 @@ function ($message) { ), ), + //JSON validation with an empty array without errors + array( + v::each( + v::keySet( + v::key("nested", v::keySet( + v::key("id", v::intVal()) + )) + ) + ), + null, + false, + array(), + 'JSON', + array(), + ), + //Complex JSON validation with array without errors array( v::each( From 5c0581a032dbf185d74e6d9d5d9524d9afad210f Mon Sep 17 00:00:00 2001 From: Davide Pastore Date: Sun, 25 Nov 2018 18:12:41 +0100 Subject: [PATCH 5/9] Add documentation related to the array validation --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index 7ed0daa..6b0ed1d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ A validation library for the Slim Framework. It internally uses [Respect/Validat - [Route parameters](#route-parameters) - [JSON requests](#json-requests) - [XML requests](#xml-requests) + - [Array](#array) - [Translate errors](#translate-errors) - [Testing](#testing) - [Contributing](#contributing) @@ -288,6 +289,42 @@ Array ``` +### Array + +If you want to validate a request that contains an array as the root level, you can directly use [Respect/Validation][respect-validation] as the first parameter of the Validation middleware. For example: + +```php +use Respect\Validation\Validator as v; + +$app = new \Slim\App(); + +//Create the validators +$validators = v::each( + v::keySet( + v::key("id", v::intVal()), + v::key("key", v::stringType()) + ) +); +``` + + +If you'll have an error, the result would be: + +```php +//In your route +$errors = $req->getAttribute('errors'); + +print_r($errors); +/* +Array +( + [0] => Must have keys { "id", "key" } + +) +*/ +``` + + ### Translate errors You can provide a callable function to translate the errors. From bab72b2ef48f09f573e8e66d0cd5377698b51dd1 Mon Sep 17 00:00:00 2001 From: Davide Pastore Date: Sun, 25 Nov 2018 19:35:44 +0100 Subject: [PATCH 6/9] Improve comment on setupGet --- tests/ValidationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index d31cd7e..cf8ebc8 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -31,7 +31,7 @@ class ValidationTest extends \PHPUnit_Framework_TestCase protected $response; /** - * Run before each test. + * Setup for the GET JSON requests. */ public function setupGet() { From 728b363320603795cf129c009e77126b19228008 Mon Sep 17 00:00:00 2001 From: Davide Pastore Date: Thu, 3 Jan 2019 15:43:36 +0100 Subject: [PATCH 7/9] First attempt to mix validation of POST/GET parameters In this case there are POST parameters and the additional GET parameter shouldn't be considered during the validation --- tests/ValidationTest.php | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index cf8ebc8..233718c 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -853,4 +853,58 @@ public function routeParamValidationProvider() ), ); } + + public function testArrayParametersWithGet() { + $uri = Uri::createFromString('https://example.com:443/foo/bar?test=1'); + $headers = new Headers(); + $headers->set('Content-Type', 'application/json;charset=utf8'); + $cookies = []; + $env = Environment::mock([ + 'SCRIPT_NAME' => '/index.php', + 'REQUEST_URI' => '/foo', + 'REQUEST_METHOD' => 'POST', + ]); + $serverParams = $env->all(); + $body = new RequestBody(); + $json = array( + array( + 'id' => 1234 + ), + ); + $body->write(json_encode($json)); + $this->request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body); + $this->response = new Response(); + $expectedValidators = v::allOf( + v::each( + v::keySet( + v::key("id", v::intVal()) + ) + ), + v::key('test', v::stringType()) + ); + $expectedTranslator = null; + $expectedErrors = array(); + $mw = new Validation($expectedValidators, $expectedTranslator); + + $errors = null; + $hasErrors = null; + $validators = null; + $translator = null; + $next = function ($req, $res) use (&$errors, &$hasErrors, &$validators, &$translator) { + $errors = $req->getAttribute('errors'); + $hasErrors = $req->getAttribute('has_errors'); + $validators = $req->getAttribute('validators'); + $translator = $req->getAttribute('translator'); + + return $res; + }; + + $response = $mw($this->request, $this->response, $next); + + $this->assertEquals($expectedErrors, $errors); + $this->assertEquals(false, $hasErrors); + + $this->assertEquals($expectedValidators, $validators); + $this->assertEquals($expectedTranslator, $translator); + } } From 6904fc71fe92d7744a5248761bd78dcdf0c9440d Mon Sep 17 00:00:00 2001 From: Davide Pastore Date: Thu, 3 Jan 2019 15:59:06 +0100 Subject: [PATCH 8/9] Fix the style --- tests/ValidationTest.php | 105 ++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 52 deletions(-) diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index 233718c..15f5467 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -854,57 +854,58 @@ public function routeParamValidationProvider() ); } - public function testArrayParametersWithGet() { - $uri = Uri::createFromString('https://example.com:443/foo/bar?test=1'); - $headers = new Headers(); - $headers->set('Content-Type', 'application/json;charset=utf8'); - $cookies = []; - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo', - 'REQUEST_METHOD' => 'POST', - ]); - $serverParams = $env->all(); - $body = new RequestBody(); - $json = array( - array( - 'id' => 1234 - ), - ); - $body->write(json_encode($json)); - $this->request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body); - $this->response = new Response(); - $expectedValidators = v::allOf( - v::each( - v::keySet( - v::key("id", v::intVal()) - ) - ), - v::key('test', v::stringType()) - ); - $expectedTranslator = null; - $expectedErrors = array(); - $mw = new Validation($expectedValidators, $expectedTranslator); - - $errors = null; - $hasErrors = null; - $validators = null; - $translator = null; - $next = function ($req, $res) use (&$errors, &$hasErrors, &$validators, &$translator) { - $errors = $req->getAttribute('errors'); - $hasErrors = $req->getAttribute('has_errors'); - $validators = $req->getAttribute('validators'); - $translator = $req->getAttribute('translator'); - - return $res; - }; - - $response = $mw($this->request, $this->response, $next); - - $this->assertEquals($expectedErrors, $errors); - $this->assertEquals(false, $hasErrors); - - $this->assertEquals($expectedValidators, $validators); - $this->assertEquals($expectedTranslator, $translator); + public function testArrayParametersWithGet() + { + $uri = Uri::createFromString('https://example.com:443/foo/bar?test=1'); + $headers = new Headers(); + $headers->set('Content-Type', 'application/json;charset=utf8'); + $cookies = []; + $env = Environment::mock([ + 'SCRIPT_NAME' => '/index.php', + 'REQUEST_URI' => '/foo', + 'REQUEST_METHOD' => 'POST', + ]); + $serverParams = $env->all(); + $body = new RequestBody(); + $json = array( + array( + 'id' => 1234 + ), + ); + $body->write(json_encode($json)); + $this->request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body); + $this->response = new Response(); + $expectedValidators = v::allOf( + v::each( + v::keySet( + v::key("id", v::intVal()) + ) + ), + v::key('test', v::stringType()) + ); + $expectedTranslator = null; + $expectedErrors = array(); + $mw = new Validation($expectedValidators, $expectedTranslator); + + $errors = null; + $hasErrors = null; + $validators = null; + $translator = null; + $next = function ($req, $res) use (&$errors, &$hasErrors, &$validators, &$translator) { + $errors = $req->getAttribute('errors'); + $hasErrors = $req->getAttribute('has_errors'); + $validators = $req->getAttribute('validators'); + $translator = $req->getAttribute('translator'); + + return $res; + }; + + $response = $mw($this->request, $this->response, $next); + + $this->assertEquals($expectedErrors, $errors); + $this->assertEquals(false, $hasErrors); + + $this->assertEquals($expectedValidators, $validators); + $this->assertEquals($expectedTranslator, $translator); } } From 15de8fdde7b17548cac803505c51afe83c8c18a9 Mon Sep 17 00:00:00 2001 From: Davide Pastore Date: Sat, 22 Jun 2019 20:35:45 +0200 Subject: [PATCH 9/9] Add tests for validation with dependencies --- tests/ValidationTest.php | 96 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index 15f5467..6371ef9 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -377,6 +377,102 @@ function ($message) { ), ), + //Complex JSON validation with key with dependencies without errors (part 1) + array( + v::key('state', v::subdivisionCode('US')->notOptional()) + ->when( + v::key('state', v::equals('NY')), // if state = NY + v::key('email', v::notOptional()->email()), // then add validation to email + v::alwaysValid() // else email is always valid + ) + ->when( + v::key('state', v::equals('NY')), // if state = NY + v::key('license', v::notOptional()), // then make license required + v::alwaysValid() // else license is always valid + ), + null, + false, + array(), + 'JSON', + array( + 'state' => 'CA' + ), + ), + + //Complex JSON validation with key with dependencies without errors (part 2) + array( + v::key('state', v::subdivisionCode('US')->notOptional()) + ->when( + v::key('state', v::equals('NY')), // if state = NY + v::key('email', v::notOptional()->email()), // then add validation to email + v::alwaysValid() // else email is always valid + ) + ->when( + v::key('state', v::equals('NY')), // if state = NY + v::key('license', v::notOptional()), // then make license required + v::alwaysValid() // else license is always valid + ), + null, + false, + array(), + 'JSON', + array( + 'state' => 'NY', + 'email' => 'test@testfoo.com', + 'license' => 'GNU' + ), + ), + + //Complex JSON validation with key with dependencies with errors (part 1) + array( + v::key('state', v::subdivisionCode('US')->notOptional()) + ->when( + v::key('state', v::equals('NY')), // if state = NY + v::key('email', v::notOptional()->email()), // then add validation to email + v::alwaysValid() // else email is always valid + ) + ->when( + v::key('state', v::equals('NY')), // if state = NY + v::key('license', v::notOptional()), // then make license required + v::alwaysValid() // else license is always valid + ), + null, + true, + array( + 'state must be a subdivision code of United States' + ), + 'JSON', + array( + 'state' => 'SP' + ), + ), + + //Complex JSON validation with key with dependencies with errors (part 2) + array( + v::key('state', v::subdivisionCode('US')->notOptional()) + ->when( + v::key('state', v::equals('NY')), // if state = NY + v::key('email', v::notOptional()->email()), // then add validation to email + v::alwaysValid() // else email is always valid + ) + ->when( + v::key('state', v::equals('NY')), // if state = NY + v::key('license', v::notOptional()), // then make license required + v::alwaysValid() // else license is always valid + ), + null, + true, + array( + 'email must be valid email', + 'Key license must be present' + ), + 'JSON', + array( + 'state' => 'NY', + 'email' => '!not a valid email!' + ), + ), + //Complex JSON validation without errors array( array(