From 07ce0a71cf23992e3950bff70b1ee46828fd0f00 Mon Sep 17 00:00:00 2001 From: Elie NEHME Date: Thu, 5 Dec 2019 21:02:19 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20a=20collection=20rule=20to=20?= =?UTF-8?q?validate=20collection=20data=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Rule/CollectionRule.php | 135 ++++++++++++++++++++++++++++++ tests/Rule/CollectionRuleTest.php | 102 ++++++++++++++++++++++ tests/ValidatorTest.php | 29 +++++++ 3 files changed, 266 insertions(+) create mode 100644 src/Rule/CollectionRule.php create mode 100644 tests/Rule/CollectionRuleTest.php diff --git a/src/Rule/CollectionRule.php b/src/Rule/CollectionRule.php new file mode 100644 index 0000000..d4c22f2 --- /dev/null +++ b/src/Rule/CollectionRule.php @@ -0,0 +1,135 @@ + {bool:optional:false by default}, + * 'messages' => {array:optional:key/value message patterns}, + * 'rules' => {array:optional:list of rules with their params}, + * 'json' => {boolean:optional:false by default}, + * ] + * + * $params = [
+ * 'required' => true,
+ * 'rules' => [
+ * ['code', StringRule::class, 'min' => 1, 'max' => 255],
+ * ['email', EmailRule::class],
+ * ] + * ] + *
+ * + * Value is considered valid if 'rules' is empty + */ + public function __construct($key, $value, array $params = []) + { + parent::__construct($key, $value, $params); + + if (isset($params[self::RULES])) { + $this->rules = $params[self::RULES]; + } + + if (isset($params[self::JSON])) { + $this->decode = (bool) $params[self::JSON]; + } + + $this->messages = $this->messages + [ + self::INVALID_VALUE => _("%key%: %value% is not in a collection"), + ]; + } + + public function validate(): int + { + $run = parent::validate(); + + if ($run !== self::CHECK) { + return $run; + } + + return $this->rules === [] ? self::VALID : $this->isValid(); + } + + protected function isValid(): int + { + $this->error = ''; + + $collection = $this->decode ? json_decode($this->value, true) : $this->value; + + if (! is_array($collection)) { + return $this->setAndReturnError(self::INVALID_VALUE); + } + + $validatedContext = []; + foreach ($collection as $data) { + $validatedData = []; + foreach ($this->rules as $rule) { + $class = $this->resolve($rule, $data); + if ($class->validate() === RuleInterface::ERROR) { + $this->error = $class->getError(); + return RuleInterface::ERROR; + } + $validatedData[$class->getKey()] = $class->getValue(); + } + $validatedContext[] = $validatedData; + } + + $this->value = $validatedContext; + + return RuleInterface::VALID; + } + + protected function resolve(array $rule, $data): RuleInterface + { + // The first element must be the key context + $key = $rule[0]; + // The second element must be the class validator name + $class = $rule[1]; + + return new $class($key, $data[$key] ?? null, $rule); + } + + public function getValue() + { + // don't change value on error or if it is not empty + if ($this->value || $this->error) { + return $this->value; + } + + return []; + } + + /** + * Empty value is null or [] only. + * + * @return bool + */ + protected function isEmpty(): bool + { + return $this->value === null || $this->value === []; + } +} diff --git a/tests/Rule/CollectionRuleTest.php b/tests/Rule/CollectionRuleTest.php new file mode 100644 index 0000000..cf35e5e --- /dev/null +++ b/tests/Rule/CollectionRuleTest.php @@ -0,0 +1,102 @@ + 80], + ['slug', MatchRule::class, MatchRule::PATTERN => '/^[a-z]{1,5}$/i'], + ]; + + $data = [ + ['code' => 12, 'slug' => 'one'], + ['code' => 13, 'slug' => 'two'], + ['code' => 15, 'slug' => 'three'], + ]; + + $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]); + + assertThat($rule->validate(), is(true)); + + $tags = $rule->getValue(); + + assertThat($tags, arrayWithSize(3)); + } + + public function testJsonValidate() + { + $rules = [ + ['code', NumericRule::class, NumericRule::MAX => 80], + ['slug', MatchRule::class, MatchRule::PATTERN => '/^[a-z]{1,5}$/i'], + ]; + + $data = json_encode([ + ['code' => 12, 'slug' => 'one'], + ['code' => 13, 'slug' => 'two'], + ['code' => 15, 'slug' => 'three'], + ]); + + $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules, CollectionRule::JSON => true]); + + assertThat($rule->validate(), is(true)); + + $tags = $rule->getValue(); + + assertThat($tags, arrayWithSize(3)); + } + + public function testValidateEmptyData() + { + $rules = [ + ['code', NumericRule::class, NumericRule::MAX => 80], + ]; + + $data = null; + + $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]); + + assertThat($rule->validate(), is(true)); + assertThat($rule->getValue(), arrayValue()); // value cast to array + } + + public function testValidateOnError() + { + $rules = [ + ['code', NumericRule::class, NumericRule::MAX => 80], + ['slug', MatchRule::class, MatchRule::PATTERN => '/^[a-z]{1,3}$/i'], + ]; + + $data = [ + ['code' => 12, 'slug' => 'one'], + ['code' => 13, 'slug' => 'two'], + ['code' => 15, 'slug' => 'three'], + ]; + + $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]); + + assertThat($rule->validate(), is(false)); + assertThat($rule->getError(), containsString('slug: three does not match /^[a-z]{1,3}$/i')); + } + + public function testValidateErrorFormat() + { + $data = '{email: "elie29@gmail.com"}'; + $rules = [ + ['email', EmailRule::class] + ]; + + $rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]); + + assertThat($rule->validate(), is(false)); + assertThat($rule->getError(), containsString('tags: {email: "elie29@gmail.com"} is not in a collection')); + } +} diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 86c7694..7a826f7 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -6,6 +6,7 @@ use Elie\Validator\Rule\ArrayRule; use Elie\Validator\Rule\BooleanRule; +use Elie\Validator\Rule\CollectionRule; use Elie\Validator\Rule\EmailRule; use Elie\Validator\Rule\JsonRule; use Elie\Validator\Rule\MatchRule; @@ -133,6 +134,34 @@ public function testValidatorWithRawPartialValidation(): void assertThat($data, arrayWithSize(3)); } + public function testValidatorWithCollection(): void + { + $rules = [ + ['email', EmailRule::class, EmailRule::REQUIRED => true], + ['tags', CollectionRule::class, CollectionRule::RULES => [ + ['code', NumericRule::class, NumericRule::MAX => 80], + ['slug', MatchRule::class, MatchRule::PATTERN => '/^[a-z]{1,5}$/i'], + ]], + ]; + + $data = [ + 'email' => 'elie29@gmail.com', + 'tags' => [ + ['code' => 12, 'slug' => 'one'], + ['code' => 13, 'slug' => 'two'], + ['code' => 15, 'slug' => 'three'], + ], + ]; + + $validator = new Validator($data, $rules); + + assertThat($validator->validate(), is(true)); + + $tags = $validator->getValidatedContext()['tags']; + + assertThat($tags, arrayWithSize(3)); + } + public function testValidatorGetImplodedErrors(): void { $validator = new Validator(['name' => 'Ben'], [