Переглянути джерело

parse custom rules and closure rules

Missael H. Anda 2 роки тому
батько
коміт
11b05e9712
1 змінених файлів з 310 додано та 293 видалено
  1. 310 293
      src/Extracting/ParsesValidationRules.php

+ 310 - 293
src/Extracting/ParsesValidationRules.php

@@ -11,6 +11,7 @@ use Knuckles\Scribe\Exceptions\ProblemParsingValidationRules;
 use Knuckles\Scribe\Exceptions\ScribeException;
 use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
 use Knuckles\Scribe\Tools\WritingUtils as w;
+use ReflectionClass;
 use Throwable;
 
 trait ParsesValidationRules
@@ -164,300 +165,322 @@ trait ParsesValidationRules
      */
     protected function parseRule($rule, array &$parameterData, bool $independentOnly, array $allParameters = []): bool
     {
-        try {
-            if (!(is_string($rule) || $rule instanceof Rule)) {
-                return true;
-            }
+        if ($rule instanceof Rule) {
+            if (method_exists($rule, 'docs')) {
+                $customData = call_user_func_array([$rule, 'docs'], []) ?: [];
 
-            // Convert string rules into rule + arguments (eg "in:1,2" becomes ["in", ["1", "2"]])
-            $parsedRule = $this->parseStringRuleIntoRuleAndArguments($rule);
-            [$rule, $arguments] = $parsedRule;
+                if (isset($customData['description'])) {
+                    $parameterData['description'] .= ' ' . $customData['description'];
+                    unset($customData['description']);
+                }
 
-            $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;
+                $parameterData = array_merge($parameterData, $customData);
             }
+        } elseif ($rule instanceof \Closure) {
+            $reflection = new \ReflectionFunction($rule);
+
+            if (is_string($description = $reflection->getDocComment())) {
+                $finalDescription = '';
+                // Cleanup comment block and extract just the description
+                foreach (explode("\n", $description) as $line) {
+                    $cleaned = preg_replace(['/^\/\*+\s*/', '/^\*+\s*/', '/\*+\/$/'], '', trim($line));
+                    if ($cleaned != '') $finalDescription .= ' ' . $cleaned;
+                }
 
-            // 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;
-                case 'accepted':
-                    $parameterData['required'] = true;
-                    $parameterData['type'] = 'boolean';
-                    $parameterData['description'] .= ' Must be accepted.';
-                    $parameterData['setter'] = fn() => 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 () use ($parameterData) {
-                        return $this->generateDummyValue('string', ['name' => $parameterData['name']]);
-                    };
-                    $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 <code>Africa/Accra</code>.";
-                    $parameterData['setter'] = $this->getFakeFactoryByName('timezone');
-                    break;
-                case 'email':
-                    $parameterData['description'] .= ' ' . $this->getDescription($rule);
-                    $parameterData['setter'] = $this->getFakeFactoryByName('email');
-                    $parameterData['type'] = 'string';
-                    break;
-                case 'url':
-                    $parameterData['setter'] = $this->getFakeFactoryByName('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 <code>{$arguments[0]}</code>.";
-                    $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' => "<code>{$arguments[0]}</code>"]);
-                    // 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' => "<code>{$arguments[0]}</code>"]);
-                    $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'] = $this->getFakeFactoryByName('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()->numerify(str_repeat("#", $arguments[0]));
-                    $parameterData['type'] = 'string';
-                    break;
-                case 'digits_between':
-                    $parameterData['description'] .= ' ' . $this->getDescription($rule, [':min' => $arguments[0], ':max' => $arguments[1]]);
-                    $parameterData['setter'] = fn() => $this->getFaker()->numerify(str_repeat("#", rand($arguments[0], $arguments[1])));
-                    $parameterData['type'] = 'string';
-                    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'], ['size' => $arguments[0]]);
-                    break;
-                case 'min':
-                    $parameterData['description'] .= ' ' . $this->getDescription(
-                            $rule, [':min' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
-                        );
-                    $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], floatval($arguments[0]), fieldName: $parameterData['name']);
-                    break;
-                case 'max':
-                    $parameterData['description'] .= ' ' . $this->getDescription(
-                            $rule, [':max' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
-                        );
-                    $max = min($arguments[0], 25);
-                    $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], 1, $max, $parameterData['name']);
-                    break;
-                case 'between':
-                    $parameterData['description'] .= ' ' . $this->getDescription(
-                            $rule, [':min' => $arguments[0], ':max' => $arguments[1]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
-                        );
-                    // Avoid exponentially complex operations by using the minimum length
-                    $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], floatval($arguments[0]), floatval($arguments[0]) + 1, $parameterData['name']);
-                    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' => "<code>{$arguments[0]}</code>", ':value' => w::getListOfValuesAsFriendlyHtmlString(array_slice($arguments, 1))]
-                        ) . ' ';
-                    break;
-                case 'required_unless':
-                    $parameterData['description'] .= ' ' . $this->getDescription(
-                            $rule, [':other' => "<code>" . array_shift($arguments) . "</code>", ':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 'accepted_if':
-                    $parameterData['type'] = 'boolean';
-                    $parameterData['description'] .= " Must be accepted when <code>$arguments[0]</code> is " . w::getListOfValuesAsFriendlyHtmlString(array_slice($arguments, 1));
-                    $parameterData['setter'] = fn() => true;
-                    break;
-                case 'same':
-                case 'different':
-                    $parameterData['description'] .= ' ' . $this->getDescription(
-                            $rule, [':other' => "<code>{$arguments[0]}</code>"]
-                        ) . ' ';
-                    break;
-
-                default:
-                    // Other rules not supported
-                    break;
+                $parameterData['description'] .= $finalDescription;
             }
+        } elseif (is_string($rule)) {
+            try {
+                // Convert string rules into rule + arguments (eg "in:1,2" becomes ["in", ["1", "2"]])
+                $parsedRule = $this->parseStringRuleIntoRuleAndArguments($rule);
+                [$rule, $arguments] = $parsedRule;
 
-            return true;
-        } catch (Throwable $e) {
-            throw CouldntProcessValidationRule::forParam($parameterData['name'], $rule, $e);
+                $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;
+                    case 'accepted':
+                        $parameterData['required'] = true;
+                        $parameterData['type'] = 'boolean';
+                        $parameterData['description'] .= ' Must be accepted.';
+                        $parameterData['setter'] = fn() => 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 () use ($parameterData) {
+                            return $this->generateDummyValue('string', ['name' => $parameterData['name']]);
+                        };
+                        $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 <code>Africa/Accra</code>.";
+                        $parameterData['setter'] = $this->getFakeFactoryByName('timezone');
+                        break;
+                    case 'email':
+                        $parameterData['description'] .= ' ' . $this->getDescription($rule);
+                        $parameterData['setter'] = $this->getFakeFactoryByName('email');
+                        $parameterData['type'] = 'string';
+                        break;
+                    case 'url':
+                        $parameterData['setter'] = $this->getFakeFactoryByName('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 <code>{$arguments[0]}</code>.";
+                        $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' => "<code>{$arguments[0]}</code>"]);
+                        // 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' => "<code>{$arguments[0]}</code>"]);
+                        $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'] = $this->getFakeFactoryByName('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()->numerify(str_repeat("#", $arguments[0]));
+                        $parameterData['type'] = 'string';
+                        break;
+                    case 'digits_between':
+                        $parameterData['description'] .= ' ' . $this->getDescription($rule, [':min' => $arguments[0], ':max' => $arguments[1]]);
+                        $parameterData['setter'] = fn() => $this->getFaker()->numerify(str_repeat("#", rand($arguments[0], $arguments[1])));
+                        $parameterData['type'] = 'string';
+                        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'], ['size' => $arguments[0]]);
+                        break;
+                    case 'min':
+                        $parameterData['description'] .= ' ' . $this->getDescription(
+                                $rule, [':min' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
+                            );
+                        $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], floatval($arguments[0]), fieldName: $parameterData['name']);
+                        break;
+                    case 'max':
+                        $parameterData['description'] .= ' ' . $this->getDescription(
+                                $rule, [':max' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
+                            );
+                        $max = min($arguments[0], 25);
+                        $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], 1, $max, $parameterData['name']);
+                        break;
+                    case 'between':
+                        $parameterData['description'] .= ' ' . $this->getDescription(
+                                $rule, [':min' => $arguments[0], ':max' => $arguments[1]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
+                            );
+                        // Avoid exponentially complex operations by using the minimum length
+                        $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], floatval($arguments[0]), floatval($arguments[0]) + 1, $parameterData['name']);
+                        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' => "<code>{$arguments[0]}</code>", ':value' => w::getListOfValuesAsFriendlyHtmlString(array_slice($arguments, 1))]
+                            ) . ' ';
+                        break;
+                    case 'required_unless':
+                        $parameterData['description'] .= ' ' . $this->getDescription(
+                                $rule, [':other' => "<code>" . array_shift($arguments) . "</code>", ':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 'accepted_if':
+                        $parameterData['type'] = 'boolean';
+                        $parameterData['description'] .= " Must be accepted when <code>$arguments[0]</code> is " . w::getListOfValuesAsFriendlyHtmlString(array_slice($arguments, 1));
+                        $parameterData['setter'] = fn() => true;
+                        break;
+                    case 'same':
+                    case 'different':
+                        $parameterData['description'] .= ' ' . $this->getDescription(
+                                $rule, [':other' => "<code>{$arguments[0]}</code>"]
+                            ) . ' ';
+                        break;
+
+                    default:
+                        // Other rules not supported
+                        break;
+                }
+            } catch (Throwable $e) {
+                throw CouldntProcessValidationRule::forParam($parameterData['name'], $rule, $e);
+            }
         }
+
+        return true;
     }
 
     /**
@@ -474,12 +497,6 @@ trait ParsesValidationRules
     {
         $ruleArguments = [];
 
-        // Convert any custom Rule objects to strings
-        if ($rule instanceof Rule) {
-            $className = substr(strrchr(get_class($rule), "\\"), 1);
-            return [$className, []];
-        }
-
         if (strpos($rule, ':') !== false) {
             [$rule, $argumentsString] = explode(':', $rule, 2);