From 71fd558c4cccce013fd4fe60e501b67c2eeef4cd Mon Sep 17 00:00:00 2001 From: Viktor Turskyi Date: Sat, 7 Nov 2026 23:39:19 +0200 Subject: [PATCH 1/4] Update the LIVR 2.0 test suite and fix the rules it checks The test suite is copied from the LIVR repository again. As before, the copy keeps negative/22-not_empty_list and negative/27-any_object adjusted, as PHP decodes {} and [] to the same array. These are the fixes of LIVR 2.0 behavior from section 1 of the LIVR 2.5 specification. - Aliases and "or" are transparent: their rules see the siblings of the field, so equal_to_field compares with a sibling there. In an item of list_of a reference to another field is a missing value (it compared the item with itself). The data a field rule gets is not trimmed again. - eq and one_of compare booleans as strings: {"eq": true} accepts true and "true", {"eq": "false"} accepts false. What LIVR 2.0 accepted still passes: {"eq": true} accepts "1" and 1, {"one_of": ["1"]} accepts true. - Meta rules and like ignore extra arguments instead of losing the custom rules of the validator. - The test runners read only the directories of the test suite. --- lib/Validator/LIVR.php | 7 ++-- lib/Validator/LIVR/Rules/Meta.php | 25 +++++++++---- lib/Validator/LIVR/Rules/Text.php | 36 +++++++++++++++---- tests/01-livr_test_suite.php | 4 +-- tests/02-livr_test_suite_aliased_rules.php | 4 +-- tests/test_suite/negative/08-like/errors.json | 5 +++ tests/test_suite/negative/08-like/input.json | 5 +++ tests/test_suite/negative/08-like/rules.json | 5 +++ .../negative/19-list_of/errors.json | 5 +-- .../test_suite/negative/19-list_of/input.json | 5 +-- .../test_suite/negative/19-list_of/rules.json | 5 +-- .../negative/20-list_of_objects/errors.json | 5 +-- .../negative/20-list_of_objects/input.json | 5 +-- .../negative/20-list_of_objects/rules.json | 5 +-- .../21-list_of_different_objects/errors.json | 5 +-- .../21-list_of_different_objects/input.json | 5 +-- .../21-list_of_different_objects/rules.json | 5 +-- .../negative/24-iso_date/errors.json | 1 + .../negative/24-iso_date/input.json | 1 + .../negative/24-iso_date/rules.json | 1 + tests/test_suite/positive/08-like/input.json | 1 + tests/test_suite/positive/08-like/output.json | 1 + tests/test_suite/positive/08-like/rules.json | 1 + .../test_suite/positive/11-decimal/input.json | 6 +++- .../positive/11-decimal/output.json | 6 +++- .../test_suite/positive/11-decimal/rules.json | 8 +++-- .../positive/12-positive_decimal/input.json | 3 +- .../positive/12-positive_decimal/output.json | 3 +- .../positive/12-positive_decimal/rules.json | 5 +-- tests/test_suite/positive/16-email/input.json | 5 +-- .../test_suite/positive/16-email/output.json | 5 +-- tests/test_suite/positive/16-email/rules.json | 5 +-- .../positive/18-nested_object/input.json | 3 +- .../positive/18-nested_object/output.json | 3 +- .../positive/18-nested_object/rules.json | 3 +- .../positive/24-iso_date/input.json | 1 + .../positive/24-iso_date/output.json | 1 + .../positive/24-iso_date/rules.json | 1 + .../test_suite/positive/26-string/input.json | 3 +- .../test_suite/positive/26-string/output.json | 3 +- .../test_suite/positive/26-string/rules.json | 3 +- tests/test_suite/positive/29-or/rules.json | 3 +- 42 files changed, 153 insertions(+), 59 deletions(-) diff --git a/lib/Validator/LIVR.php b/lib/Validator/LIVR.php index 72606d1..812771e 100644 --- a/lib/Validator/LIVR.php +++ b/lib/Validator/LIVR.php @@ -243,9 +243,9 @@ public function getFieldRule($fieldName) return null; } + // $allValues come from the caller as they are, usually already trimmed data if ($this->isAutoTrim) { - $value = $this->autoTrim($value); - $allValues = $this->autoTrim($allValues); + $value = $this->autoTrim($value); } $output = $value; @@ -326,9 +326,10 @@ private static function buildAliasedRule($alias) $validateValue = $validator->registerRules($ruleBuilders)->getFieldRule('value'); $aliasError = isset($alias['error']) ? $alias['error'] : null; + // Aliases are transparent: their rules see the siblings of the field that uses the alias return function ($value, $params, &$outputArr) use ($validateValue, $aliasError) { $output = null; - $errCode = $validateValue($value, array('value' => $value), $output); + $errCode = $validateValue($value, $params, $output); if ($errCode) { return $aliasError !== null ? $aliasError : $errCode; diff --git a/lib/Validator/LIVR/Rules/Meta.php b/lib/Validator/LIVR/Rules/Meta.php index 351a9a1..b87a8dc 100644 --- a/lib/Validator/LIVR/Rules/Meta.php +++ b/lib/Validator/LIVR/Rules/Meta.php @@ -6,6 +6,9 @@ class Meta { public static function nestedObject($livr, $ruleBuilders) { + // The rule builders are the last argument: extra arguments are ignored, as in LIVR 2.0 + $ruleBuilders = func_get_arg(func_num_args() - 1); + $validator = new \Validator\LIVR($livr); $validator->registerRules($ruleBuilders)->prepare(); @@ -34,8 +37,9 @@ public static function listOf() $first_arg = func_get_arg(0); if (\Validator\LIVR\Util::isList($first_arg)) { + // The rule builders are the last argument: extra arguments are ignored $livr = func_get_arg(0); - $ruleBuilders = func_get_arg(1); + $ruleBuilders = func_get_arg(func_num_args() - 1); } else { $livr = func_get_args(); $ruleBuilders = array_pop($livr); @@ -58,9 +62,9 @@ public static function listOf() $hasErrors = false; foreach ($values as $value) { - // Rules of an item see it as the only field of the data, like in validate() + // Items have no containing object: a reference in their rules is a missing value $output = null; - $errCode = $validateItem($value, array('field' => $value), $output); + $errCode = $validateItem($value, array(), $output); if ($errCode) { $results[] = null; @@ -83,6 +87,9 @@ public static function listOf() public static function listOfObjects($livr, $ruleBuilders) { + // The rule builders are the last argument: extra arguments are ignored, as in LIVR 2.0 + $ruleBuilders = func_get_arg(func_num_args() - 1); + $validator = new \Validator\LIVR($livr); $validator->registerRules($ruleBuilders)->prepare(); @@ -123,6 +130,9 @@ public static function listOfObjects($livr, $ruleBuilders) public static function listOfDifferentObjects($selectorField, $livrs, $ruleBuilders) { + // The rule builders are the last argument: extra arguments are ignored, as in LIVR 2.0 + $ruleBuilders = func_get_arg(func_num_args() - 1); + $validators = array(); foreach ($livrs as $selectorValue => $livr) { @@ -176,6 +186,9 @@ public static function listOfDifferentObjects($selectorField, $livrs, $ruleBuild public static function variableObject($selectorField, $livrs, $ruleBuilders) { + // The rule builders are the last argument: extra arguments are ignored, as in LIVR 2.0 + $ruleBuilders = func_get_arg(func_num_args() - 1); + $validators = array(); foreach ($livrs as $selectorValue => $livr) { $validator = new \Validator\LIVR($livr); @@ -220,13 +233,13 @@ public static function __or() } return function ($value, $params, &$outputArr) use ($fieldRules) { - // Empty values go to the rule sets too, so "required" and "not_empty" in them work + // Empty values go to the rule sets too, so "required" and "not_empty" in them work. + // The rule sets are transparent: their rules see the siblings of the field $lastError = null; - $allValues = array('field' => $value); foreach ($fieldRules as $fieldRule) { $output = null; - $errCode = $fieldRule($value, $allValues, $output); + $errCode = $fieldRule($value, $params, $output); if (!$errCode) { $outputArr = $output; diff --git a/lib/Validator/LIVR/Rules/Text.php b/lib/Validator/LIVR/Rules/Text.php index 69e30e2..63ac6e7 100644 --- a/lib/Validator/LIVR/Rules/Text.php +++ b/lib/Validator/LIVR/Rules/Text.php @@ -15,10 +15,16 @@ public static function oneOf() array_pop($allowedValues); # pop rule_builders } - // The allowed values as keys: isset() does not go through the whole list like in_array() + // The allowed values as keys: isset() does not go through the whole list like in_array(). + // Booleans are compared as strings (LIVR 2.5, 2.3): true is "true". As in LIVR 2.0, it is + // also "1", the PHP string form of true (and false is ""), so the values that passed still pass $allowedValuesSet = array(); foreach ($allowedValues as $v) { $allowedValuesSet[(string) $v] = true; + + if (is_bool($v)) { + $allowedValuesSet[$v ? 'true' : 'false'] = true; + } } return function ($value) use ($allowedValuesSet) { @@ -30,17 +36,27 @@ public static function oneOf() return 'FORMAT_ERROR'; } - if (!isset($allowedValuesSet[(string) $value])) { - return 'NOT_ALLOWED_VALUE'; + if (isset($allowedValuesSet[(string) $value])) { + return; } - return; + if (is_bool($value) && isset($allowedValuesSet[$value ? 'true' : 'false'])) { + return; + } + + return 'NOT_ALLOWED_VALUE'; }; } public static function eq($allowedValue) { - return function ($value, $params) use ($allowedValue) { + // Booleans are compared as strings (LIVR 2.5, 2.3): {"eq": true} accepts true and "true", + // and {"eq": "true"} accepts true. As in LIVR 2.0, the PHP string forms are compared too + // (true is "1", false is ""), so {"eq": true} still accepts "1" and 1, and {"eq": "1"} true + $allowedString = is_array($allowedValue) ? null : (string) $allowedValue; + $allowedBoolString = is_bool($allowedValue) ? ($allowedValue ? 'true' : 'false') : null; + + return function ($value, $params) use ($allowedString, $allowedBoolString) { if (!isset($value) || $value === '') { return; } @@ -49,7 +65,13 @@ public static function eq($allowedValue) return 'FORMAT_ERROR'; } - if ((string)$value === (string)$allowedValue) { + $string = (string) $value; + + if ($string === $allowedString || $string === $allowedBoolString) { + return; + } + + if (is_bool($value) && ($value ? 'true' : 'false') === $allowedString) { return; } @@ -168,7 +190,7 @@ public static function like($re) // D: $ matches only at the end of the value, as in JavaScript, not before a trailing newline $re = '/' . $re . '/D'; - if (func_num_args() == 3) { #Passed regexp flag + if (func_num_args() >= 3) { #Passed regexp flag, extra arguments are ignored $flags = func_get_arg(1); if ($flags && $flags != 'i') { diff --git a/tests/01-livr_test_suite.php b/tests/01-livr_test_suite.php index 4258ec9..41ff9bf 100644 --- a/tests/01-livr_test_suite.php +++ b/tests/01-livr_test_suite.php @@ -48,7 +48,7 @@ public function positiveTestsProvider() $entries = array(); while (false !== ( $entry = readdir($handle) )) { - if ($entry != "." && $entry != "..") { + if ($entry != "." && $entry != ".." && is_dir("$dir/$entry")) { $entries[] = $entry; } } @@ -80,7 +80,7 @@ public function negativeTestsProvider() $entries = array(); while (false !== ( $entry = readdir($handle) )) { - if ($entry != "." && $entry != "..") { + if ($entry != "." && $entry != ".." && is_dir("$dir/$entry")) { $entries[] = $entry; } } diff --git a/tests/02-livr_test_suite_aliased_rules.php b/tests/02-livr_test_suite_aliased_rules.php index 0e22f3f..e1d543e 100644 --- a/tests/02-livr_test_suite_aliased_rules.php +++ b/tests/02-livr_test_suite_aliased_rules.php @@ -53,7 +53,7 @@ public function positiveTestsProvider() $entries = array(); while (false !== ( $entry = readdir($handle) )) { - if ($entry != "." && $entry != "..") { + if ($entry != "." && $entry != ".." && is_dir("$dir/$entry")) { $entries[] = $entry; } } @@ -86,7 +86,7 @@ public function negativeTestsProvider() $entries = array(); while (false !== ( $entry = readdir($handle) )) { - if ($entry != "." && $entry != "..") { + if ($entry != "." && $entry != ".." && is_dir("$dir/$entry")) { $entries[] = $entry; } } diff --git a/tests/test_suite/negative/08-like/errors.json b/tests/test_suite/negative/08-like/errors.json index 95e9b3f..87351f9 100644 --- a/tests/test_suite/negative/08-like/errors.json +++ b/tests/test_suite/negative/08-like/errors.json @@ -4,6 +4,11 @@ "middle_name": "WRONG_FORMAT", "age": "WRONG_FORMAT", + "zip_trailing_newline": "WRONG_FORMAT", + "zip_text_after_newline": "WRONG_FORMAT", + "zip_text_before_newline": "WRONG_FORMAT", + "code_trailing_newline": "WRONG_FORMAT", + "value_is_hash": "FORMAT_ERROR", "value_is_empty_hash": "FORMAT_ERROR", "value_is_array": "FORMAT_ERROR", diff --git a/tests/test_suite/negative/08-like/input.json b/tests/test_suite/negative/08-like/input.json index 78956ad..4e68837 100644 --- a/tests/test_suite/negative/08-like/input.json +++ b/tests/test_suite/negative/08-like/input.json @@ -7,6 +7,11 @@ "extra_name": "extra_name", + "zip_trailing_newline": "12345\n", + "zip_text_after_newline": "12345\n