From 0c4da381da7505afec6dd8e8ed082dcc4e1b7a3d Mon Sep 17 00:00:00 2001 From: shalvah Date: Wed, 28 Jul 2021 20:02:46 +0100 Subject: [PATCH] Better error messaging for failed validation rule parsing --- .../CouldntProcessValidationRule.php | 16 + .../ProblemParsingValidationRules.php | 15 + src/Extracting/ParsesValidationRules.php | 682 +++++++++--------- 3 files changed, 383 insertions(+), 330 deletions(-) create mode 100644 src/Exceptions/CouldntProcessValidationRule.php create mode 100644 src/Exceptions/ProblemParsingValidationRules.php diff --git a/src/Exceptions/CouldntProcessValidationRule.php b/src/Exceptions/CouldntProcessValidationRule.php new file mode 100644 index 00000000..1ec9e475 --- /dev/null +++ b/src/Exceptions/CouldntProcessValidationRule.php @@ -0,0 +1,16 @@ + $ruleset) { - if (count($customParameterData) && !isset($customParameterData[$parameter])) { - c::debug($this->getMissingCustomDataMessage($parameter)); - } - $userSpecifiedParameterInfo = $customParameterData[$parameter] ?? []; + try { + if (count($customParameterData) && !isset($customParameterData[$parameter])) { + c::debug($this->getMissingCustomDataMessage($parameter)); + } + $userSpecifiedParameterInfo = $customParameterData[$parameter] ?? []; - // Make sure the user-specified description comes first (and add full stops where needed). - $description = $userSpecifiedParameterInfo['description'] ?? ''; - if (!empty($description) && !Str::endsWith($description, '.')) { - $description .= '.'; - } - $parameterData = [ - 'name' => $parameter, - 'required' => false, - 'type' => null, - 'example' => self::$MISSING_VALUE, - 'description' => $description, - ]; - $dependentRules[$parameter] = []; - - // First, parse only "independent" rules - foreach ($ruleset as $rule) { - $parsed = $this->parseRule($rule, $parameterData, true); - if (!$parsed) { - $dependentRules[$parameter][] = $rule; + // Make sure the user-specified description comes first (and add full stops where needed). + $description = $userSpecifiedParameterInfo['description'] ?? ''; + if (!empty($description) && !Str::endsWith($description, '.')) { + $description .= '.'; + } + $parameterData = [ + 'name' => $parameter, + 'required' => false, + 'type' => null, + 'example' => self::$MISSING_VALUE, + 'description' => $description, + ]; + $dependentRules[$parameter] = []; + + // First, parse only "independent" rules + foreach ($ruleset as $rule) { + $parsed = $this->parseRule($rule, $parameterData, true); + if (!$parsed) { + $dependentRules[$parameter][] = $rule; + } } - } - $parameterData['description'] = trim($parameterData['description']); + $parameterData['description'] = trim($parameterData['description']); - // Set a default type - if (is_null($parameterData['type'])) { - $parameterData['type'] = 'string'; - } + // Set a default type + if (is_null($parameterData['type'])) { + $parameterData['type'] = 'string'; + } - $parameterData['name'] = $parameter; - $parameters[$parameter] = $parameterData; + $parameterData['name'] = $parameter; + $parameters[$parameter] = $parameterData; + } catch (Throwable $e) { + if ($e instanceof ScribeException) { + throw $e; + } + throw ProblemParsingValidationRules::forParam($parameter, $e); + } } // Now parse any "dependent" rules and set examples. At this point, we should know all field's types. foreach ($dependentRules as $parameter => $ruleset) { - $parameterData = $parameters[$parameter]; - $userSpecifiedParameterInfo = $customParameterData[$parameter] ?? []; + try { + $parameterData = $parameters[$parameter]; + $userSpecifiedParameterInfo = $customParameterData[$parameter] ?? []; - foreach ($ruleset as $rule) { - $this->parseRule($rule, $parameterData, false, $parameters); - } + foreach ($ruleset as $rule) { + $this->parseRule($rule, $parameterData, false, $parameters); + } - // Make sure the user-specified example overwrites others. - if (isset($userSpecifiedParameterInfo['example'])) { - if ($this->shouldCastUserExample()) { - // Examples in comments are strings, we need to cast them properly - $parameterData['example'] = $this->castToType($userSpecifiedParameterInfo['example'], $parameterData['type'] ?? 'string'); - } else { - $parameterData['example'] = $userSpecifiedParameterInfo['example']; + // Make sure the user-specified example overwrites others. + if (isset($userSpecifiedParameterInfo['example'])) { + if ($this->shouldCastUserExample()) { + // Examples in comments are strings, we need to cast them properly + $parameterData['example'] = $this->castToType($userSpecifiedParameterInfo['example'], $parameterData['type'] ?? 'string'); + } else { + $parameterData['example'] = $userSpecifiedParameterInfo['example']; + } } - } - // End descriptions with a full stop - $parameterData['description'] = trim($parameterData['description']); - if (!empty($parameterData['description']) && !Str::endsWith($parameterData['description'], '.')) { - $parameterData['description'] .= '.'; - } + // End descriptions with a full stop + $parameterData['description'] = trim($parameterData['description']); + if (!empty($parameterData['description']) && !Str::endsWith($parameterData['description'], '.')) { + $parameterData['description'] .= '.'; + } - $parameterData = $this->updateParameterExample($parameterData); - $parameters[$parameter] = $parameterData; + $parameterData = $this->updateParameterExample($parameterData); + $parameters[$parameter] = $parameterData; + } catch (Throwable $e) { + if ($e instanceof ScribeException) { + throw $e; + } + throw ProblemParsingValidationRules::forParam($parameter, $e); + } } return $parameters; @@ -143,289 +161,293 @@ protected function normaliseRules(array $rules): array */ protected function parseRule($rule, array &$parameterData, bool $independentOnly, array $allParameters = []): bool { - if (!(is_string($rule) || $rule instanceof Rule)) { - return true; - } + try { + if (!(is_string($rule) || $rule instanceof Rule)) { + return true; + } - // Convert string rules into rule + arguments (eg "in:1,2" becomes ["in", ["1", "2"]]) - $parsedRule = $this->parseStringRuleIntoRuleAndArguments($rule); - [$rule, $arguments] = $parsedRule; + // Convert string rules into rule + arguments (eg "in:1,2" becomes ["in", ["1", "2"]]) + $parsedRule = $this->parseStringRuleIntoRuleAndArguments($rule); + [$rule, $arguments] = $parsedRule; - $dependentRules = ['between', 'max', 'min', 'size', 'gt', 'gte', 'lt', 'lte', 'before', 'after', 'before_or_equal', 'after_or_equal']; - if ($independentOnly && in_array($rule, $dependentRules)) { - return false; - } + $dependentRules = ['between', 'max', 'min', 'size', 'gt', 'gte', 'lt', 'lte', 'before', 'after', 'before_or_equal', 'after_or_equal']; + if ($independentOnly && in_array($rule, $dependentRules)) { + return false; + } - // Reminders: - // 1. Append to the description (with a leading space); don't overwrite. - // 2. Avoid testing on the value of $parameterData['type'], - // as that may not have been set yet, since the rules can be in any order. - // For this reason, only deterministic rules are supported - // 3. All rules supported must be rules that we can generate a valid dummy value for. - switch ($rule) { - case 'required': - $parameterData['required'] = true; - break; - - /* - * Primitive types. No description should be added - */ - case 'bool': - case 'boolean': - $parameterData['setter'] = function () { - return Arr::random([true, false]); - }; - $parameterData['type'] = 'boolean'; - break; - case 'string': - $parameterData['setter'] = function () { - return $this->generateDummyValue('string'); - }; - $parameterData['type'] = 'string'; - break; - case 'int': - case 'integer': - $parameterData['setter'] = function () { - return $this->generateDummyValue('integer'); - }; - $parameterData['type'] = 'integer'; - break; - case 'numeric': - $parameterData['setter'] = function () { - return $this->generateDummyValue('number'); - }; - $parameterData['type'] = 'number'; - break; - case 'array': - $parameterData['setter'] = function () { - return [$this->generateDummyValue('string')]; - }; - $parameterData['type'] = 'array'; // The cleanup code in normaliseArrayAndObjectParameters() will set this to a valid type (x[] or object) - break; - case 'file': - $parameterData['type'] = 'file'; - $parameterData['description'] .= ' Must be a file.'; - $parameterData['setter'] = function () { - return $this->generateDummyValue('file'); - }; - break; - - /** - * Special string types - */ - case 'alpha': - $parameterData['description'] .= " Must contain only letters."; - $parameterData['setter'] = function () { - return $this->getFaker()->lexify('??????'); - }; - break; - case 'alpha_dash': - $parameterData['description'] .= " Must contain only letters, numbers, dashes and underscores."; - $parameterData['setter'] = function () { - return $this->getFaker()->lexify('???-???_?'); - }; - break; - case 'alpha_num': - $parameterData['description'] .= " Must contain only letters and numbers."; - $parameterData['setter'] = function () { - return $this->getFaker()->bothify('#?#???#'); - }; - break; - case 'timezone': - // Laravel's message merely says "The value must be a valid zone" - $parameterData['description'] .= " Must be a valid time zone, such as Africa/Accra."; - $parameterData['setter'] = function () { - return $this->getFaker()->timezone; - }; - break; - case 'email': - $parameterData['description'] .= ' ' . $this->getDescription($rule); - $parameterData['setter'] = function () { - return $this->getFaker()->safeEmail; - }; - $parameterData['type'] = 'string'; - break; - case 'url': - $parameterData['setter'] = function () { - return $this->getFaker()->url; - }; - $parameterData['type'] = 'string'; - // Laravel's message is "The value format is invalid". Ugh.🤮 - $parameterData['description'] .= " Must be a valid URL."; - break; - case 'ip': - $parameterData['description'] .= ' ' . $this->getDescription($rule); - $parameterData['type'] = 'string'; - $parameterData['setter'] = function () { - return $this->getFaker()->ipv4; - }; - break; - case 'json': - $parameterData['type'] = 'string'; - $parameterData['description'] .= ' ' . $this->getDescription($rule); - $parameterData['setter'] = function () { - return json_encode([$this->getFaker()->word, $this->getFaker()->word,]); - }; - break; - case 'date': - $parameterData['type'] = 'string'; - $parameterData['description'] .= ' ' . $this->getDescription($rule); - $parameterData['setter'] = fn() => date('Y-m-d\TH:i:s', time()); - break; - case 'date_format': - $parameterData['type'] = 'string'; - // Laravel description here is "The value must match the format Y-m-d". Not descriptive enough. - $parameterData['description'] .= " Must be a valid date in the format {$arguments[0]}."; - $parameterData['setter'] = function () use ($arguments) { - return date($arguments[0], time()); - }; - break; - case 'after': - case 'after_or_equal': - $parameterData['type'] = 'string'; - $parameterData['description'] .= ' ' . $this->getDescription($rule, [':date' => "{$arguments[0]}"]); - // TODO introduce the concept of "modifiers", like date_format - // The startDate may refer to another field, in which case, we just ignore it for now. - $startDate = isset($allParameters[$arguments[0]]) ? 'today' : $arguments[0]; - $parameterData['setter'] = fn() => $this->getFaker()->dateTimeBetween($startDate, '+100 years')->format('Y-m-d'); - break; - case 'before': - case 'before_or_equal': - $parameterData['type'] = 'string'; - // The argument can be either another field or a date - // The endDate may refer to another field, in which case, we just ignore it for now. - $endDate = isset($allParameters[$arguments[0]]) ? 'today' : $arguments[0]; - $parameterData['description'] .= ' ' . $this->getDescription($rule, [':date' => "{$arguments[0]}"]); - $parameterData['setter'] = fn() => $this->getFaker()->dateTimeBetween('-30 years', $endDate)->format('Y-m-d'); - break; - case 'starts_with': - $parameterData['description'] .= ' Must start with one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments); - $parameterData['setter'] = fn() => $this->getFaker()->lexify("{$arguments[0]}????");; - break; - case 'ends_with': - $parameterData['description'] .= ' Must end with one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments); - $parameterData['setter'] = fn() => $this->getFaker()->lexify("????{$arguments[0]}");; - break; - case 'uuid': - $parameterData['description'] .= ' ' . $this->getDescription($rule) . ' '; - $parameterData['setter'] = fn() => $this->getFaker()->uuid();; - break; - case 'regex': - $parameterData['description'] .= ' ' . $this->getDescription($rule, [':regex' => $arguments[0]]); - $parameterData['setter'] = fn() => $this->getFaker()->regexify($arguments[0]);; - break; - - /** - * Special number types. - */ - case 'digits': - $parameterData['description'] .= ' ' . $this->getDescription($rule, [':digits' => $arguments[0]]); - $parameterData['setter'] = fn() => $this->getFaker()->randomNumber($arguments[0], true); - $parameterData['type'] = 'number'; - break; - case 'digits_between': - $parameterData['description'] .= ' ' . $this->getDescription($rule, [':min' => $arguments[0], ':max' => $arguments[1]]); - $parameterData['setter'] = fn() => $this->getFaker()->randomNumber($this->getFaker()->numberBetween(...$arguments), true); - $parameterData['type'] = 'number'; - break; - - /** - * These rules can apply to numbers, strings, arrays or files - */ - case 'size': - $parameterData['description'] .= ' ' . $this->getDescription( - $rule, [':size' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type']) - ); - $parameterData['setter'] = $this->getDummyValueGenerator($parameterData['type'], $arguments[0]); - break; - case 'min': - $parameterData['description'] .= ' ' . $this->getDescription( - $rule, [':min' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type']) - ); - $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], $arguments[0]); - break; - case 'max': - $parameterData['description'] .= ' ' . $this->getDescription( - $rule, [':max' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type']) - ); - $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], 0, $arguments[0]); - break; - case 'between': - $parameterData['description'] .= ' ' . $this->getDescription( - $rule, [':min' => $arguments[0], ':max' => $arguments[1]], $this->getLaravelValidationBaseTypeMapping($parameterData['type']) - ); - $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], $arguments[0], $arguments[1]); - break; - - /** - * Special file types. - */ - case 'image': - $parameterData['type'] = 'file'; - $parameterData['description'] .= ' ' . $this->getDescription($rule) . ' '; - $parameterData['setter'] = function () { - // This is fine because the file example generator generates an image - return $this->generateDummyValue('file'); - }; - break; - - /** - * Other rules. - */ - case 'in': - // Not using the rule description here because it only says "The attribute is invalid" - $parameterData['description'] .= ' Must be one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments) . ' '; - $parameterData['setter'] = function () use ($arguments) { - return Arr::random($arguments); - }; - break; - - /** - * These rules only add a description. Generating valid examples is too complex. - */ - case 'not_in': - $parameterData['description'] .= ' Must not be one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments) . ' '; - break; - case 'required_if': - $parameterData['description'] .= ' ' . $this->getDescription( - $rule, [':other' => "{$arguments[0]}", ':value' => "{$arguments[1]}"] - ) . ' '; - break; - case 'required_unless': - $parameterData['description'] .= ' ' . $this->getDescription( - $rule, [':other' => "" . array_shift($arguments) . "", ':values' => w::getListOfValuesAsFriendlyHtmlString($arguments)] - ) . ' '; - break; - case 'required_with': - $parameterData['description'] .= ' ' . $this->getDescription( - $rule, [':values' => w::getListOfValuesAsFriendlyHtmlString($arguments)] - ) . ' '; - break; - case 'required_without': - $description = $this->getDescription( - $rule, [':values' => w::getListOfValuesAsFriendlyHtmlString($arguments)] - ) . ' '; - $parameterData['description'] .= str_replace('must be present', 'is not present', $description); - break; - case 'required_with_all': - case 'required_without_all': - $parameterData['description'] .= ' ' . $this->getDescription( - $rule, [':values' => w::getListOfValuesAsFriendlyHtmlString($arguments, "and")] - ) . ' '; - break; - case 'same': - case 'different': - $parameterData['description'] .= ' ' . $this->getDescription( - $rule, [':other' => "{$arguments[0]}"] - ) . ' '; - break; - - default: - // Other rules not supported - break; - } + // Reminders: + // 1. Append to the description (with a leading space); don't overwrite. + // 2. Avoid testing on the value of $parameterData['type'], + // as that may not have been set yet, since the rules can be in any order. + // For this reason, only deterministic rules are supported + // 3. All rules supported must be rules that we can generate a valid dummy value for. + switch ($rule) { + case 'required': + $parameterData['required'] = true; + break; + + /* + * Primitive types. No description should be added + */ + case 'bool': + case 'boolean': + $parameterData['setter'] = function () { + return Arr::random([true, false]); + }; + $parameterData['type'] = 'boolean'; + break; + case 'string': + $parameterData['setter'] = function () { + return $this->generateDummyValue('string'); + }; + $parameterData['type'] = 'string'; + break; + case 'int': + case 'integer': + $parameterData['setter'] = function () { + return $this->generateDummyValue('integer'); + }; + $parameterData['type'] = 'integer'; + break; + case 'numeric': + $parameterData['setter'] = function () { + return $this->generateDummyValue('number'); + }; + $parameterData['type'] = 'number'; + break; + case 'array': + $parameterData['setter'] = function () { + return [$this->generateDummyValue('string')]; + }; + $parameterData['type'] = 'array'; // The cleanup code in normaliseArrayAndObjectParameters() will set this to a valid type (x[] or object) + break; + case 'file': + $parameterData['type'] = 'file'; + $parameterData['description'] .= ' Must be a file.'; + $parameterData['setter'] = function () { + return $this->generateDummyValue('file'); + }; + break; + + /** + * Special string types + */ + case 'alpha': + $parameterData['description'] .= " Must contain only letters."; + $parameterData['setter'] = function () { + return $this->getFaker()->lexify('??????'); + }; + break; + case 'alpha_dash': + $parameterData['description'] .= " Must contain only letters, numbers, dashes and underscores."; + $parameterData['setter'] = function () { + return $this->getFaker()->lexify('???-???_?'); + }; + break; + case 'alpha_num': + $parameterData['description'] .= " Must contain only letters and numbers."; + $parameterData['setter'] = function () { + return $this->getFaker()->bothify('#?#???#'); + }; + break; + case 'timezone': + // Laravel's message merely says "The value must be a valid zone" + $parameterData['description'] .= " Must be a valid time zone, such as Africa/Accra."; + $parameterData['setter'] = function () { + return $this->getFaker()->timezone; + }; + break; + case 'email': + $parameterData['description'] .= ' ' . $this->getDescription($rule); + $parameterData['setter'] = function () { + return $this->getFaker()->safeEmail; + }; + $parameterData['type'] = 'string'; + break; + case 'url': + $parameterData['setter'] = function () { + return $this->getFaker()->url; + }; + $parameterData['type'] = 'string'; + // Laravel's message is "The value format is invalid". Ugh.🤮 + $parameterData['description'] .= " Must be a valid URL."; + break; + case 'ip': + $parameterData['description'] .= ' ' . $this->getDescription($rule); + $parameterData['type'] = 'string'; + $parameterData['setter'] = function () { + return $this->getFaker()->ipv4; + }; + break; + case 'json': + $parameterData['type'] = 'string'; + $parameterData['description'] .= ' ' . $this->getDescription($rule); + $parameterData['setter'] = function () { + return json_encode([$this->getFaker()->word, $this->getFaker()->word,]); + }; + break; + case 'date': + $parameterData['type'] = 'string'; + $parameterData['description'] .= ' ' . $this->getDescription($rule); + $parameterData['setter'] = fn() => date('Y-m-d\TH:i:s', time()); + break; + case 'date_format': + $parameterData['type'] = 'string'; + // Laravel description here is "The value must match the format Y-m-d". Not descriptive enough. + $parameterData['description'] .= " Must be a valid date in the format {$arguments[0]}."; + $parameterData['setter'] = function () use ($arguments) { + return date($arguments[0], time()); + }; + break; + case 'after': + case 'after_or_equal': + $parameterData['type'] = 'string'; + $parameterData['description'] .= ' ' . $this->getDescription($rule, [':date' => "{$arguments[0]}"]); + // TODO introduce the concept of "modifiers", like date_format + // The startDate may refer to another field, in which case, we just ignore it for now. + $startDate = isset($allParameters[$arguments[0]]) ? 'today' : $arguments[0]; + $parameterData['setter'] = fn() => $this->getFaker()->dateTimeBetween($startDate, '+100 years')->format('Y-m-d'); + break; + case 'before': + case 'before_or_equal': + $parameterData['type'] = 'string'; + // The argument can be either another field or a date + // The endDate may refer to another field, in which case, we just ignore it for now. + $endDate = isset($allParameters[$arguments[0]]) ? 'today' : $arguments[0]; + $parameterData['description'] .= ' ' . $this->getDescription($rule, [':date' => "{$arguments[0]}"]); + $parameterData['setter'] = fn() => $this->getFaker()->dateTimeBetween('-30 years', $endDate)->format('Y-m-d'); + break; + case 'starts_with': + $parameterData['description'] .= ' Must start with one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments); + $parameterData['setter'] = fn() => $this->getFaker()->lexify("{$arguments[0]}????");; + break; + case 'ends_with': + $parameterData['description'] .= ' Must end with one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments); + $parameterData['setter'] = fn() => $this->getFaker()->lexify("????{$arguments[0]}");; + break; + case 'uuid': + $parameterData['description'] .= ' ' . $this->getDescription($rule) . ' '; + $parameterData['setter'] = fn() => $this->getFaker()->uuid();; + break; + case 'regex': + $parameterData['description'] .= ' ' . $this->getDescription($rule, [':regex' => $arguments[0]]); + $parameterData['setter'] = fn() => $this->getFaker()->regexify($arguments[0]);; + break; + + /** + * Special number types. + */ + case 'digits': + $parameterData['description'] .= ' ' . $this->getDescription($rule, [':digits' => $arguments[0]]); + $parameterData['setter'] = fn() => $this->getFaker()->randomNumber($arguments[0], true); + $parameterData['type'] = 'number'; + break; + case 'digits_between': + $parameterData['description'] .= ' ' . $this->getDescription($rule, [':min' => $arguments[0], ':max' => $arguments[1]]); + $parameterData['setter'] = fn() => $this->getFaker()->randomNumber($this->getFaker()->numberBetween(...$arguments), true); + $parameterData['type'] = 'number'; + break; + + /** + * These rules can apply to numbers, strings, arrays or files + */ + case 'size': + $parameterData['description'] .= ' ' . $this->getDescription( + $rule, [':size' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type']) + ); + $parameterData['setter'] = $this->getDummyValueGenerator($parameterData['type'], $arguments[0]); + break; + case 'min': + $parameterData['description'] .= ' ' . $this->getDescription( + $rule, [':min' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type']) + ); + $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], $arguments[0]); + break; + case 'max': + $parameterData['description'] .= ' ' . $this->getDescription( + $rule, [':max' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type']) + ); + $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], 0, $arguments[0]); + break; + case 'between': + $parameterData['description'] .= ' ' . $this->getDescription( + $rule, [':min' => $arguments[0], ':max' => $arguments[1]], $this->getLaravelValidationBaseTypeMapping($parameterData['type']) + ); + $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], $arguments[0], $arguments[1]); + break; + + /** + * Special file types. + */ + case 'image': + $parameterData['type'] = 'file'; + $parameterData['description'] .= ' ' . $this->getDescription($rule) . ' '; + $parameterData['setter'] = function () { + // This is fine because the file example generator generates an image + return $this->generateDummyValue('file'); + }; + break; + + /** + * Other rules. + */ + case 'in': + // Not using the rule description here because it only says "The attribute is invalid" + $parameterData['description'] .= ' Must be one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments) . ' '; + $parameterData['setter'] = function () use ($arguments) { + return Arr::random($arguments); + }; + break; + + /** + * These rules only add a description. Generating valid examples is too complex. + */ + case 'not_in': + $parameterData['description'] .= ' Must not be one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments) . ' '; + break; + case 'required_if': + $parameterData['description'] .= ' ' . $this->getDescription( + $rule, [':other' => "{$arguments[0]}", ':value' => "{$arguments[1]}"] + ) . ' '; + break; + case 'required_unless': + $parameterData['description'] .= ' ' . $this->getDescription( + $rule, [':other' => "" . array_shift($arguments) . "", ':values' => w::getListOfValuesAsFriendlyHtmlString($arguments)] + ) . ' '; + break; + case 'required_with': + $parameterData['description'] .= ' ' . $this->getDescription( + $rule, [':values' => w::getListOfValuesAsFriendlyHtmlString($arguments)] + ) . ' '; + break; + case 'required_without': + $description = $this->getDescription( + $rule, [':values' => w::getListOfValuesAsFriendlyHtmlString($arguments)] + ) . ' '; + $parameterData['description'] .= str_replace('must be present', 'is not present', $description); + break; + case 'required_with_all': + case 'required_without_all': + $parameterData['description'] .= ' ' . $this->getDescription( + $rule, [':values' => w::getListOfValuesAsFriendlyHtmlString($arguments, "and")] + ) . ' '; + break; + case 'same': + case 'different': + $parameterData['description'] .= ' ' . $this->getDescription( + $rule, [':other' => "{$arguments[0]}"] + ) . ' '; + break; + + default: + // Other rules not supported + break; + } - return true; + return true; + } catch (Throwable $e) { + throw CouldntProcessValidationRule::forParam($parameterData['name'], $rule, $e); + } } /**