|
@@ -2,6 +2,7 @@
|
|
|
|
|
|
namespace Knuckles\Scribe\Extracting;
|
|
namespace Knuckles\Scribe\Extracting;
|
|
|
|
|
|
|
|
+use Closure;
|
|
use Illuminate\Contracts\Validation\Rule;
|
|
use Illuminate\Contracts\Validation\Rule;
|
|
use Illuminate\Contracts\Validation\ValidationRule;
|
|
use Illuminate\Contracts\Validation\ValidationRule;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Arr;
|
|
@@ -14,6 +15,7 @@ use Knuckles\Scribe\Exceptions\ScribeException;
|
|
use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
|
|
use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
|
|
use Knuckles\Scribe\Tools\WritingUtils as w;
|
|
use Knuckles\Scribe\Tools\WritingUtils as w;
|
|
use ReflectionClass;
|
|
use ReflectionClass;
|
|
|
|
+use ReflectionFunction;
|
|
use Throwable;
|
|
use Throwable;
|
|
|
|
|
|
trait ParsesValidationRules
|
|
trait ParsesValidationRules
|
|
@@ -22,22 +24,20 @@ trait ParsesValidationRules
|
|
|
|
|
|
public static \stdClass $MISSING_VALUE;
|
|
public static \stdClass $MISSING_VALUE;
|
|
|
|
|
|
- public function getParametersFromValidationRules(array $validationRules, array $customParameterData = []): array
|
|
|
|
|
|
+ public function getParametersFromValidationRules(array $validationRulesByParameters, array $customParameterData = []): array
|
|
{
|
|
{
|
|
self::$MISSING_VALUE = new \stdClass();
|
|
self::$MISSING_VALUE = new \stdClass();
|
|
- $validationRules = $this->normaliseRules($validationRules);
|
|
|
|
|
|
+ $validationRulesByParameters = $this->normaliseRules($validationRulesByParameters);
|
|
|
|
|
|
$parameters = [];
|
|
$parameters = [];
|
|
- $dependentRules = [];
|
|
|
|
- foreach ($validationRules as $parameter => $ruleset) {
|
|
|
|
|
|
+ $rulesWhichDependOnType = ['between', 'max', 'min', 'size', 'gt', 'gte', 'lt', 'lte', 'before', 'after', 'before_or_equal', 'after_or_equal'];
|
|
|
|
+ foreach ($validationRulesByParameters as $parameter => $ruleset) {
|
|
|
|
+ $userSpecifiedParameterInfo = $customParameterData[$parameter] ?? [];
|
|
|
|
+ $stringRules = array_filter($ruleset, fn($rule) => is_string($rule));
|
|
|
|
+ $rulesWhichRefineAType = ['before', 'after', 'before_or_equal', 'after_or_equal'];
|
|
|
|
+
|
|
try {
|
|
try {
|
|
- $parameterPlusDot = $parameter . '.';
|
|
|
|
- if (count($customParameterData) && !isset($customParameterData[$parameter])
|
|
|
|
- && ! Arr::first(array_keys($customParameterData), fn ($key) => str_starts_with($key, $parameterPlusDot))
|
|
|
|
- ) {
|
|
|
|
- c::debug($this->getMissingCustomDataMessage($parameter));
|
|
|
|
- }
|
|
|
|
- $userSpecifiedParameterInfo = $customParameterData[$parameter] ?? [];
|
|
|
|
|
|
+ $this->warnAboutMissingCustomParameterData($parameter, $customParameterData);
|
|
|
|
|
|
// Make sure the user-specified description comes first (and add full stops where needed).
|
|
// Make sure the user-specified description comes first (and add full stops where needed).
|
|
$description = $userSpecifiedParameterInfo['description'] ?? '';
|
|
$description = $userSpecifiedParameterInfo['description'] ?? '';
|
|
@@ -52,51 +52,65 @@ trait ParsesValidationRules
|
|
'description' => $description,
|
|
'description' => $description,
|
|
'nullable' => false,
|
|
'nullable' => false,
|
|
];
|
|
];
|
|
- $dependentRules[$parameter] = [];
|
|
|
|
|
|
|
|
- // First, parse only "independent" rules
|
|
|
|
- foreach ($ruleset as $rule) {
|
|
|
|
- $parsed = $this->parseRule($rule, $parameterData, true);
|
|
|
|
- if (!$parsed) {
|
|
|
|
- $dependentRules[$parameter][] = $rule;
|
|
|
|
- }
|
|
|
|
|
|
+ $closureRules = array_filter($ruleset, fn($rule) => ($rule instanceof ClosureValidationRule || $rule instanceof Closure));
|
|
|
|
+ foreach ($closureRules as $rule) {
|
|
|
|
+ $this->processClosureRule($rule, $parameterData);
|
|
}
|
|
}
|
|
|
|
|
|
- $parameterData['description'] = trim($parameterData['description']);
|
|
|
|
|
|
+ $enumValidationRules = array_filter($ruleset, fn($rule) => $rule instanceof \Illuminate\Validation\Rules\Enum);
|
|
|
|
+ foreach ($enumValidationRules as $rule) {
|
|
|
|
+ $this->processEnumValidationRule($rule, $parameterData);
|
|
|
|
+ }
|
|
|
|
|
|
- // Set a default type
|
|
|
|
- if (is_null($parameterData['type'])) {
|
|
|
|
- $parameterData['type'] = 'string';
|
|
|
|
|
|
+ $ruleObjects = array_filter($ruleset, fn($rule) => ($rule instanceof Rule || $rule instanceof ValidationRule));
|
|
|
|
+ foreach ($ruleObjects as $rule) {
|
|
|
|
+ $this->processRuleObject($rule, $parameterData);
|
|
}
|
|
}
|
|
|
|
|
|
- $parameterData['name'] = $parameter;
|
|
|
|
|
|
+ // TODO support more rules
|
|
|
|
+ // Process in 3 passes
|
|
|
|
+ // 1. Rules which provide no info about type or example
|
|
|
|
+ // (required, required_*, same, different, nullable, exists, and others in the "utilities" group)
|
|
|
|
+ // 2. Rules which set a type.
|
|
|
|
+ // 3. Rules whose processing depends on the type. ('between', 'max', 'min', 'size', 'gt', 'gte', 'lt', 'lte')
|
|
|
|
+ // - Note: 'in' does not provide type info (does it?), but is enough to generate an example
|
|
|
|
+
|
|
|
|
+ // First pass: process rules which provide no type or example info
|
|
|
|
+ $firstPassRuleNames = [
|
|
|
|
+ "required",
|
|
|
|
+ "required_*",
|
|
|
|
+ "same",
|
|
|
|
+ "different",
|
|
|
|
+ "nullable",
|
|
|
|
+ ];
|
|
|
|
|
|
- if ($parameterData['required'] === true){
|
|
|
|
- $parameterData['nullable'] = false;
|
|
|
|
|
|
+ $firstPassRules = array_filter($stringRules, fn($rule) => Str::is($firstPassRuleNames, $rule));
|
|
|
|
+ foreach ($firstPassRules as $rule) {
|
|
|
|
+ $this->processRule($rule, $parameterData);
|
|
}
|
|
}
|
|
|
|
|
|
- $parameters[$parameter] = $parameterData;
|
|
|
|
- } catch (Throwable $e) {
|
|
|
|
- if ($e instanceof ScribeException) {
|
|
|
|
- // This is a lower-level error that we've encountered and wrapped;
|
|
|
|
- // Pass it on to the user.
|
|
|
|
- throw $e;
|
|
|
|
|
|
+ $secondPassRules = array_filter($stringRules, fn($rule) => !Str::is($firstPassRuleNames, $rule) && !in_array($rule, $rulesWhichDependOnType));
|
|
|
|
+ foreach ($secondPassRules as $rule) {
|
|
|
|
+ $this->processRule($rule, $parameterData);
|
|
}
|
|
}
|
|
- 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) {
|
|
|
|
- try {
|
|
|
|
- $parameterData = $parameters[$parameter];
|
|
|
|
- $userSpecifiedParameterInfo = $customParameterData[$parameter] ?? [];
|
|
|
|
|
|
+ // The second pass should have set a type. If not, set a default type
|
|
|
|
+ if (is_null($parameterData['type'])) {
|
|
|
|
+ $parameterData['type'] = 'string';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ($parameterData['required'] === true) {
|
|
|
|
+ $parameterData['nullable'] = false;
|
|
|
|
+ }
|
|
|
|
|
|
- foreach ($ruleset as $rule) {
|
|
|
|
- $this->parseRule($rule, $parameterData, false, $parameters);
|
|
|
|
|
|
+ // Now parse any "dependent" rules and set examples. At this point, we should know all field's types.
|
|
|
|
+ $thirdPassRules = array_filter($stringRules, fn($rule) => in_array($rule, $rulesWhichDependOnType));
|
|
|
|
+ foreach ($thirdPassRules as $rule) {
|
|
|
|
+ $this->processRule($rule, $parameterData);
|
|
}
|
|
}
|
|
|
|
|
|
- // Make sure the user-specified example overwrites others.
|
|
|
|
|
|
+ // Make sure the user-specified example overwrites ours.
|
|
if (array_key_exists('example', $userSpecifiedParameterInfo)) {
|
|
if (array_key_exists('example', $userSpecifiedParameterInfo)) {
|
|
if ($userSpecifiedParameterInfo['example'] != null && $this->shouldCastUserExample()) {
|
|
if ($userSpecifiedParameterInfo['example'] != null && $this->shouldCastUserExample()) {
|
|
// Examples in comments are strings, we need to cast them properly
|
|
// Examples in comments are strings, we need to cast them properly
|
|
@@ -107,7 +121,6 @@ trait ParsesValidationRules
|
|
}
|
|
}
|
|
|
|
|
|
// End descriptions with a full stop
|
|
// End descriptions with a full stop
|
|
- $parameterData['description'] = trim($parameterData['description']);
|
|
|
|
if (!empty($parameterData['description']) && !Str::endsWith($parameterData['description'], '.')) {
|
|
if (!empty($parameterData['description']) && !Str::endsWith($parameterData['description'], '.')) {
|
|
$parameterData['description'] .= '.';
|
|
$parameterData['description'] .= '.';
|
|
}
|
|
}
|
|
@@ -172,98 +185,87 @@ trait ParsesValidationRules
|
|
})->toArray();
|
|
})->toArray();
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Parse a validation rule and extract a parameter type, description and setter (used to generate an example).
|
|
|
|
- *
|
|
|
|
- * If $independentOnly is true, only independent rules will be parsed.
|
|
|
|
- * If a rule depends on another parameter (eg gt:field) or needs to know the type of the parameter first (eg:
|
|
|
|
- * size:34), it will return false.
|
|
|
|
- */
|
|
|
|
- protected function parseRule($rule, array &$parameterData, bool $independentOnly, array $allParameters = []): bool
|
|
|
|
|
|
+ // For inline Closure rules, turn any comment above it into a description
|
|
|
|
+ //
|
|
|
|
+ // $request->validate([
|
|
|
|
+ // 'my_param' => [
|
|
|
|
+ // 'required',
|
|
|
|
+ // /** Must be a hexadecimal number. */
|
|
|
|
+ // function ($attribute, $value, $fail) {
|
|
|
|
+ // if (!preg_match('/^[0-9a-f]+$/', $value)) {
|
|
|
|
+ // $fail('Must be in hex format');
|
|
|
|
+ // }
|
|
|
|
+ // },
|
|
|
|
+ // ],
|
|
|
|
+ // ]);
|
|
|
|
+ protected function processClosureRule($rule, array &$parameterData): void
|
|
{
|
|
{
|
|
- // 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.
|
|
|
|
-
|
|
|
|
- if ($rule instanceof ClosureValidationRule || $rule instanceof \Closure) {
|
|
|
|
- $reflection = new \ReflectionFunction($rule instanceof ClosureValidationRule ? $rule->callback : $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;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- $parameterData['description'] .= $finalDescription;
|
|
|
|
|
|
+ $docComment = (new ReflectionFunction($rule instanceof ClosureValidationRule ? $rule->callback : $rule))
|
|
|
|
+ ->getDocComment();
|
|
|
|
+
|
|
|
|
+ if (is_string($docComment)) {
|
|
|
|
+ $description = '';
|
|
|
|
+ foreach (explode("\n", $docComment) as $line) {
|
|
|
|
+ $cleaned = preg_replace(['/\*+\/$/', '/^\/\*+\s*/', '/^\*+\s*/'], '', trim($line));
|
|
|
|
+ if ($cleaned != '') $description .= ' ' . $cleaned;
|
|
}
|
|
}
|
|
|
|
|
|
- return true;
|
|
|
|
|
|
+ $parameterData['description'] .= $description;
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- if ($rule instanceof \Illuminate\Validation\Rules\Enum) {
|
|
|
|
- $reflection = new \ReflectionClass($rule);
|
|
|
|
- $property = $reflection->getProperty('type');
|
|
|
|
- $property->setAccessible(true);
|
|
|
|
- $type = $property->getValue($rule);
|
|
|
|
-
|
|
|
|
- if (enum_exists($type) && method_exists($type, 'tryFrom')) {
|
|
|
|
- // $case->value only exists on BackedEnums, not UnitEnums
|
|
|
|
- // method_exists($enum, 'tryFrom') implies the enum is a BackedEnum
|
|
|
|
- // @phpstan-ignore-next-line
|
|
|
|
- $cases = array_map(fn ($case) => $case->value, $type::cases());
|
|
|
|
- $parameterData['type'] = gettype($cases[0]);
|
|
|
|
- $parameterData['enumValues'] = $cases;
|
|
|
|
- $parameterData['setter'] = fn () => Arr::random($cases);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return true;
|
|
|
|
|
|
+ protected function processEnumValidationRule($rule, array &$parameterData, array $allParameters = []): void
|
|
|
|
+ {
|
|
|
|
+ $property = (new ReflectionClass($rule))->getProperty('type');
|
|
|
|
+ $property->setAccessible(true);
|
|
|
|
+ $enumClass = $property->getValue($rule);
|
|
|
|
+
|
|
|
|
+ if (enum_exists($enumClass) && method_exists($enumClass, 'tryFrom')) {
|
|
|
|
+ // $case->value only exists on BackedEnums, not UnitEnums
|
|
|
|
+ // method_exists($enum, 'tryFrom') implies the enum is a BackedEnum
|
|
|
|
+ // @phpstan-ignore-next-line
|
|
|
|
+ $cases = array_map(fn($case) => $case->value, $enumClass::cases());
|
|
|
|
+ $parameterData['type'] = gettype($cases[0]);
|
|
|
|
+ $parameterData['enumValues'] = $cases;
|
|
|
|
+ $parameterData['setter'] = fn() => Arr::random($cases);
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- if ($rule instanceof Rule || $rule instanceof ValidationRule) {
|
|
|
|
- if (method_exists($rule, 'invokable')) {
|
|
|
|
- // Laravel wraps InvokableRule instances in an InvokableValidationRule class,
|
|
|
|
- // so we must retrieve the original rule
|
|
|
|
- $rule = $rule->invokable();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (method_exists($rule, 'docs')) {
|
|
|
|
- $customData = call_user_func_array([$rule, 'docs'], []) ?: [];
|
|
|
|
|
|
+ protected function processRuleObject($rule, array &$parameterData): void
|
|
|
|
+ {
|
|
|
|
+ if (method_exists($rule, 'invokable')) {
|
|
|
|
+ // Laravel wraps InvokableRule instances in an InvokableValidationRule class,
|
|
|
|
+ // so we must retrieve the original rule
|
|
|
|
+ $rule = $rule->invokable();
|
|
|
|
+ }
|
|
|
|
|
|
- if (isset($customData['description'])) {
|
|
|
|
- $parameterData['description'] .= ' ' . $customData['description'];
|
|
|
|
- }
|
|
|
|
- if (isset($customData['example'])) {
|
|
|
|
- $parameterData['setter'] = fn() => $customData['example'];
|
|
|
|
- } elseif (isset($customData['setter'])) {
|
|
|
|
- $parameterData['setter'] = $customData['setter'];
|
|
|
|
- }
|
|
|
|
|
|
+ // Users can define a custom "docs" method on a rule to give Scribe more info.
|
|
|
|
+ if (method_exists($rule, 'docs')) {
|
|
|
|
+ $customData = call_user_func_array([$rule, 'docs'], []) ?: [];
|
|
|
|
|
|
- $parameterData = array_merge($parameterData, Arr::except($customData, [
|
|
|
|
- 'description', 'example', 'setter',
|
|
|
|
- ]));
|
|
|
|
|
|
+ if (isset($customData['description'])) {
|
|
|
|
+ $parameterData['description'] .= ' ' . $customData['description'];
|
|
|
|
+ }
|
|
|
|
+ if (isset($customData['example'])) {
|
|
|
|
+ $parameterData['setter'] = fn() => $customData['example'];
|
|
|
|
+ } elseif (isset($customData['setter'])) {
|
|
|
|
+ $parameterData['setter'] = $customData['setter'];
|
|
}
|
|
}
|
|
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!is_string($rule)) {
|
|
|
|
- return false;
|
|
|
|
|
|
+ $parameterData = array_merge($parameterData, Arr::except($customData, [
|
|
|
|
+ 'description', 'example', 'setter',
|
|
|
|
+ ]));
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Parse a validation rule and extract a parameter type, description and setter (used to generate an example).
|
|
|
|
+ */
|
|
|
|
+ protected function processRule($rule, array &$parameterData, array $allParameters = []): bool
|
|
|
|
+ {
|
|
|
|
+ // Reminder: Always append to the description (with a leading space); don't overwrite.
|
|
try {
|
|
try {
|
|
- // 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;
|
|
|
|
- }
|
|
|
|
|
|
+ [$rule, $ruleArguments] = $this->parseStringRuleIntoRuleAndArguments($rule);
|
|
|
|
|
|
switch ($rule) {
|
|
switch ($rule) {
|
|
case 'required':
|
|
case 'required':
|
|
@@ -275,6 +277,11 @@ trait ParsesValidationRules
|
|
$parameterData['description'] .= ' Must be accepted.';
|
|
$parameterData['description'] .= ' Must be accepted.';
|
|
$parameterData['setter'] = fn() => true;
|
|
$parameterData['setter'] = fn() => true;
|
|
break;
|
|
break;
|
|
|
|
+ case 'accepted_if':
|
|
|
|
+ $parameterData['type'] = 'boolean';
|
|
|
|
+ $parameterData['description'] .= " Must be accepted when <code>$ruleArguments[0]</code> is " . w::getListOfValuesAsFriendlyHtmlString(array_slice($ruleArguments, 1));
|
|
|
|
+ $parameterData['setter'] = fn() => true;
|
|
|
|
+ break;
|
|
|
|
|
|
/*
|
|
/*
|
|
* Primitive types. No description should be added
|
|
* Primitive types. No description should be added
|
|
@@ -378,18 +385,18 @@ trait ParsesValidationRules
|
|
case 'date_format':
|
|
case 'date_format':
|
|
$parameterData['type'] = 'string';
|
|
$parameterData['type'] = 'string';
|
|
// Laravel description here is "The value must match the format Y-m-d". Not descriptive enough.
|
|
// 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());
|
|
|
|
|
|
+ $parameterData['description'] .= " Must be a valid date in the format <code>{$ruleArguments[0]}</code>.";
|
|
|
|
+ $parameterData['setter'] = function () use ($ruleArguments) {
|
|
|
|
+ return date($ruleArguments[0], time());
|
|
};
|
|
};
|
|
break;
|
|
break;
|
|
case 'after':
|
|
case 'after':
|
|
case 'after_or_equal':
|
|
case 'after_or_equal':
|
|
$parameterData['type'] = 'string';
|
|
$parameterData['type'] = 'string';
|
|
- $parameterData['description'] .= ' ' . $this->getDescription($rule, [':date' => "<code>{$arguments[0]}</code>"]);
|
|
|
|
|
|
+ $parameterData['description'] .= ' ' . $this->getDescription($rule, [':date' => "<code>{$ruleArguments[0]}</code>"]);
|
|
// TODO introduce the concept of "modifiers", like date_format
|
|
// 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.
|
|
// The startDate may refer to another field, in which case, we just ignore it for now.
|
|
- $startDate = isset($allParameters[$arguments[0]]) ? 'today' : $arguments[0];
|
|
|
|
|
|
+ $startDate = isset($allParameters[$ruleArguments[0]]) ? 'today' : $ruleArguments[0];
|
|
$parameterData['setter'] = fn() => $this->getFaker()->dateTimeBetween($startDate, '+100 years')->format('Y-m-d');
|
|
$parameterData['setter'] = fn() => $this->getFaker()->dateTimeBetween($startDate, '+100 years')->format('Y-m-d');
|
|
break;
|
|
break;
|
|
case 'before':
|
|
case 'before':
|
|
@@ -397,38 +404,38 @@ trait ParsesValidationRules
|
|
$parameterData['type'] = 'string';
|
|
$parameterData['type'] = 'string';
|
|
// The argument can be either another field or a date
|
|
// 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.
|
|
// 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>"]);
|
|
|
|
|
|
+ $endDate = isset($allParameters[$ruleArguments[0]]) ? 'today' : $ruleArguments[0];
|
|
|
|
+ $parameterData['description'] .= ' ' . $this->getDescription($rule, [':date' => "<code>{$ruleArguments[0]}</code>"]);
|
|
$parameterData['setter'] = fn() => $this->getFaker()->dateTimeBetween('-30 years', $endDate)->format('Y-m-d');
|
|
$parameterData['setter'] = fn() => $this->getFaker()->dateTimeBetween('-30 years', $endDate)->format('Y-m-d');
|
|
break;
|
|
break;
|
|
case 'starts_with':
|
|
case 'starts_with':
|
|
- $parameterData['description'] .= ' Must start with one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments);
|
|
|
|
- $parameterData['setter'] = fn() => $this->getFaker()->lexify("{$arguments[0]}????");;
|
|
|
|
|
|
+ $parameterData['description'] .= ' Must start with one of ' . w::getListOfValuesAsFriendlyHtmlString($ruleArguments);
|
|
|
|
+ $parameterData['setter'] = fn() => $this->getFaker()->lexify("{$ruleArguments[0]}????");;
|
|
break;
|
|
break;
|
|
case 'ends_with':
|
|
case 'ends_with':
|
|
- $parameterData['description'] .= ' Must end with one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments);
|
|
|
|
- $parameterData['setter'] = fn() => $this->getFaker()->lexify("????{$arguments[0]}");;
|
|
|
|
|
|
+ $parameterData['description'] .= ' Must end with one of ' . w::getListOfValuesAsFriendlyHtmlString($ruleArguments);
|
|
|
|
+ $parameterData['setter'] = fn() => $this->getFaker()->lexify("????{$ruleArguments[0]}");;
|
|
break;
|
|
break;
|
|
case 'uuid':
|
|
case 'uuid':
|
|
$parameterData['description'] .= ' ' . $this->getDescription($rule) . ' ';
|
|
$parameterData['description'] .= ' ' . $this->getDescription($rule) . ' ';
|
|
$parameterData['setter'] = $this->getFakeFactoryByName('uuid');
|
|
$parameterData['setter'] = $this->getFakeFactoryByName('uuid');
|
|
break;
|
|
break;
|
|
case 'regex':
|
|
case 'regex':
|
|
- $parameterData['description'] .= ' ' . $this->getDescription($rule, [':regex' => $arguments[0]]);
|
|
|
|
- $parameterData['setter'] = fn() => $this->getFaker()->regexify($arguments[0]);;
|
|
|
|
|
|
+ $parameterData['description'] .= ' ' . $this->getDescription($rule, [':regex' => $ruleArguments[0]]);
|
|
|
|
+ $parameterData['setter'] = fn() => $this->getFaker()->regexify($ruleArguments[0]);;
|
|
break;
|
|
break;
|
|
|
|
|
|
/**
|
|
/**
|
|
* Special number types.
|
|
* Special number types.
|
|
*/
|
|
*/
|
|
case 'digits':
|
|
case 'digits':
|
|
- $parameterData['description'] .= ' ' . $this->getDescription($rule, [':digits' => $arguments[0]]);
|
|
|
|
- $parameterData['setter'] = fn() => $this->getFaker()->numerify(str_repeat("#", $arguments[0]));
|
|
|
|
|
|
+ $parameterData['description'] .= ' ' . $this->getDescription($rule, [':digits' => $ruleArguments[0]]);
|
|
|
|
+ $parameterData['setter'] = fn() => $this->getFaker()->numerify(str_repeat("#", $ruleArguments[0]));
|
|
$parameterData['type'] = 'string';
|
|
$parameterData['type'] = 'string';
|
|
break;
|
|
break;
|
|
case 'digits_between':
|
|
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['description'] .= ' ' . $this->getDescription($rule, [':min' => $ruleArguments[0], ':max' => $ruleArguments[1]]);
|
|
|
|
+ $parameterData['setter'] = fn() => $this->getFaker()->numerify(str_repeat("#", rand($ruleArguments[0], $ruleArguments[1])));
|
|
$parameterData['type'] = 'string';
|
|
$parameterData['type'] = 'string';
|
|
break;
|
|
break;
|
|
|
|
|
|
@@ -437,29 +444,29 @@ trait ParsesValidationRules
|
|
*/
|
|
*/
|
|
case 'size':
|
|
case 'size':
|
|
$parameterData['description'] .= ' ' . $this->getDescription(
|
|
$parameterData['description'] .= ' ' . $this->getDescription(
|
|
- $rule, [':size' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
|
|
|
|
|
|
+ $rule, [':size' => $ruleArguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
|
|
);
|
|
);
|
|
- $parameterData['setter'] = $this->getDummyValueGenerator($parameterData['type'], ['size' => $arguments[0]]);
|
|
|
|
|
|
+ $parameterData['setter'] = $this->getDummyValueGenerator($parameterData['type'], ['size' => $ruleArguments[0]]);
|
|
break;
|
|
break;
|
|
case 'min':
|
|
case 'min':
|
|
$parameterData['description'] .= ' ' . $this->getDescription(
|
|
$parameterData['description'] .= ' ' . $this->getDescription(
|
|
- $rule, [':min' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
|
|
|
|
|
|
+ $rule, [':min' => $ruleArguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
|
|
);
|
|
);
|
|
- $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], floatval($arguments[0]), fieldName: $parameterData['name']);
|
|
|
|
|
|
+ $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], floatval($ruleArguments[0]), fieldName: $parameterData['name']);
|
|
break;
|
|
break;
|
|
case 'max':
|
|
case 'max':
|
|
$parameterData['description'] .= ' ' . $this->getDescription(
|
|
$parameterData['description'] .= ' ' . $this->getDescription(
|
|
- $rule, [':max' => $arguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
|
|
|
|
|
|
+ $rule, [':max' => $ruleArguments[0]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
|
|
);
|
|
);
|
|
- $max = min($arguments[0], 25);
|
|
|
|
|
|
+ $max = min($ruleArguments[0], 25);
|
|
$parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], 1, $max, $parameterData['name']);
|
|
$parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], 1, $max, $parameterData['name']);
|
|
break;
|
|
break;
|
|
case 'between':
|
|
case 'between':
|
|
$parameterData['description'] .= ' ' . $this->getDescription(
|
|
$parameterData['description'] .= ' ' . $this->getDescription(
|
|
- $rule, [':min' => $arguments[0], ':max' => $arguments[1]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
|
|
|
|
|
|
+ $rule, [':min' => $ruleArguments[0], ':max' => $ruleArguments[1]], $this->getLaravelValidationBaseTypeMapping($parameterData['type'])
|
|
);
|
|
);
|
|
// Avoid exponentially complex operations by using the minimum length
|
|
// Avoid exponentially complex operations by using the minimum length
|
|
- $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], floatval($arguments[0]), floatval($arguments[0]) + 1, $parameterData['name']);
|
|
|
|
|
|
+ $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], floatval($ruleArguments[0]), floatval($ruleArguments[0]) + 1, $parameterData['name']);
|
|
break;
|
|
break;
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -478,9 +485,9 @@ trait ParsesValidationRules
|
|
* Other rules.
|
|
* Other rules.
|
|
*/
|
|
*/
|
|
case 'in':
|
|
case 'in':
|
|
- $parameterData['enumValues'] = $arguments;
|
|
|
|
- $parameterData['setter'] = function () use ($arguments) {
|
|
|
|
- return Arr::random($arguments);
|
|
|
|
|
|
+ $parameterData['enumValues'] = $ruleArguments;
|
|
|
|
+ $parameterData['setter'] = function () use ($ruleArguments) {
|
|
|
|
+ return Arr::random($ruleArguments);
|
|
};
|
|
};
|
|
break;
|
|
break;
|
|
|
|
|
|
@@ -488,60 +495,55 @@ trait ParsesValidationRules
|
|
* These rules only add a description. Generating valid examples is too complex.
|
|
* These rules only add a description. Generating valid examples is too complex.
|
|
*/
|
|
*/
|
|
case 'not_in':
|
|
case 'not_in':
|
|
- $parameterData['description'] .= ' Must not be one of ' . w::getListOfValuesAsFriendlyHtmlString($arguments) . ' ';
|
|
|
|
|
|
+ $parameterData['description'] .= ' Must not be one of ' . w::getListOfValuesAsFriendlyHtmlString($ruleArguments) . ' ';
|
|
break;
|
|
break;
|
|
case 'required_if':
|
|
case 'required_if':
|
|
$parameterData['description'] .= sprintf(
|
|
$parameterData['description'] .= sprintf(
|
|
- " This field is required when <code>{$arguments[0]}</code> is %s. ",
|
|
|
|
- w::getListOfValuesAsFriendlyHtmlString(array_slice($arguments, 1))
|
|
|
|
|
|
+ " This field is required when <code>{$ruleArguments[0]}</code> is %s. ",
|
|
|
|
+ w::getListOfValuesAsFriendlyHtmlString(array_slice($ruleArguments, 1))
|
|
);
|
|
);
|
|
break;
|
|
break;
|
|
case 'required_unless':
|
|
case 'required_unless':
|
|
$parameterData['description'] .= sprintf(
|
|
$parameterData['description'] .= sprintf(
|
|
- " This field is required unless <code>{$arguments[0]}</code> is in %s. ",
|
|
|
|
- w::getListOfValuesAsFriendlyHtmlString(array_slice($arguments, 1))
|
|
|
|
|
|
+ " This field is required unless <code>{$ruleArguments[0]}</code> is in %s. ",
|
|
|
|
+ w::getListOfValuesAsFriendlyHtmlString(array_slice($ruleArguments, 1))
|
|
);
|
|
);
|
|
break;
|
|
break;
|
|
case 'required_with':
|
|
case 'required_with':
|
|
$parameterData['description'] .= sprintf(
|
|
$parameterData['description'] .= sprintf(
|
|
" This field is required when %s is present. ",
|
|
" This field is required when %s is present. ",
|
|
- w::getListOfValuesAsFriendlyHtmlString($arguments)
|
|
|
|
|
|
+ w::getListOfValuesAsFriendlyHtmlString($ruleArguments)
|
|
);
|
|
);
|
|
break;
|
|
break;
|
|
case 'required_without':
|
|
case 'required_without':
|
|
$parameterData['description'] .= sprintf(
|
|
$parameterData['description'] .= sprintf(
|
|
" This field is required when %s is not present. ",
|
|
" This field is required when %s is not present. ",
|
|
- w::getListOfValuesAsFriendlyHtmlString($arguments)
|
|
|
|
|
|
+ w::getListOfValuesAsFriendlyHtmlString($ruleArguments)
|
|
);
|
|
);
|
|
break;
|
|
break;
|
|
case 'required_with_all':
|
|
case 'required_with_all':
|
|
$parameterData['description'] .= sprintf(
|
|
$parameterData['description'] .= sprintf(
|
|
" This field is required when %s are present. ",
|
|
" This field is required when %s are present. ",
|
|
- w::getListOfValuesAsFriendlyHtmlString($arguments, "and")
|
|
|
|
|
|
+ w::getListOfValuesAsFriendlyHtmlString($ruleArguments, "and")
|
|
);
|
|
);
|
|
break;
|
|
break;
|
|
case 'required_without_all':
|
|
case 'required_without_all':
|
|
$parameterData['description'] .= sprintf(
|
|
$parameterData['description'] .= sprintf(
|
|
- " This field is required when none of %s are present. ",
|
|
|
|
- w::getListOfValuesAsFriendlyHtmlString($arguments, "and")
|
|
|
|
|
|
+ " This field is required when none of %s are present. ",
|
|
|
|
+ w::getListOfValuesAsFriendlyHtmlString($ruleArguments, "and")
|
|
);
|
|
);
|
|
break;
|
|
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 'same':
|
|
- $parameterData['description'] .= " The value and <code>{$arguments[0]}</code> must match.";
|
|
|
|
|
|
+ $parameterData['description'] .= " The value and <code>{$ruleArguments[0]}</code> must match.";
|
|
break;
|
|
break;
|
|
case 'different':
|
|
case 'different':
|
|
- $parameterData['description'] .= " The value and <code>{$arguments[0]}</code> must be different.";
|
|
|
|
|
|
+ $parameterData['description'] .= " The value and <code>{$ruleArguments[0]}</code> must be different.";
|
|
break;
|
|
break;
|
|
case 'nullable':
|
|
case 'nullable':
|
|
$parameterData['nullable'] = true;
|
|
$parameterData['nullable'] = true;
|
|
break;
|
|
break;
|
|
case 'exists':
|
|
case 'exists':
|
|
- $parameterData['description'] .= " The <code>{$arguments[1]}</code> of an existing record in the {$arguments[0]} table.";
|
|
|
|
|
|
+ $parameterData['description'] .= " The <code>{$ruleArguments[1]}</code> of an existing record in the {$ruleArguments[0]} table.";
|
|
break;
|
|
break;
|
|
default:
|
|
default:
|
|
// Other rules not supported
|
|
// Other rules not supported
|
|
@@ -551,11 +553,13 @@ trait ParsesValidationRules
|
|
throw CouldntProcessValidationRule::forParam($parameterData['name'], $rule, $e);
|
|
throw CouldntProcessValidationRule::forParam($parameterData['name'], $rule, $e);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ $parameterData['description'] = trim($parameterData['description']);
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* Parse a string rule into the base rule and arguments.
|
|
* Parse a string rule into the base rule and arguments.
|
|
|
|
+ * eg "in:1,2" becomes ["in", ["1", "2"]]
|
|
* Laravel validation rules are specified in the format {rule}:{arguments}
|
|
* Laravel validation rules are specified in the format {rule}:{arguments}
|
|
* Arguments are separated by commas.
|
|
* Arguments are separated by commas.
|
|
* For instance the rule "max:3" states that the value may only be three letters.
|
|
* For instance the rule "max:3" states that the value may only be three letters.
|
|
@@ -568,7 +572,7 @@ trait ParsesValidationRules
|
|
{
|
|
{
|
|
$ruleArguments = [];
|
|
$ruleArguments = [];
|
|
|
|
|
|
- if (strpos($rule, ':') !== false) {
|
|
|
|
|
|
+ if (str_contains($rule, ':')) {
|
|
[$rule, $argumentsString] = explode(':', $rule, 2);
|
|
[$rule, $argumentsString] = explode(':', $rule, 2);
|
|
|
|
|
|
// These rules can have commas in their arguments, so we don't split on commas
|
|
// These rules can have commas in their arguments, so we don't split on commas
|
|
@@ -595,7 +599,7 @@ trait ParsesValidationRules
|
|
: null;
|
|
: null;
|
|
}
|
|
}
|
|
} else if (!is_null($parameterData['example']) && $parameterData['example'] !== self::$MISSING_VALUE) {
|
|
} else if (!is_null($parameterData['example']) && $parameterData['example'] !== self::$MISSING_VALUE) {
|
|
- if($parameterData['example'] === 'No-example' && !$parameterData['required']){
|
|
|
|
|
|
+ if ($parameterData['example'] === 'No-example' && !$parameterData['required']) {
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
// Casting again is important since values may have been cast to string in the validator
|
|
// Casting again is important since values may have been cast to string in the validator
|
|
@@ -648,7 +652,7 @@ trait ParsesValidationRules
|
|
// 3. Otherwise, default to `object`
|
|
// 3. Otherwise, default to `object`
|
|
// Important: We're iterating in reverse, to ensure we set child items before parent items
|
|
// Important: We're iterating in reverse, to ensure we set child items before parent items
|
|
// (assuming the user specified parents first, which is the more common thing)y
|
|
// (assuming the user specified parents first, which is the more common thing)y
|
|
- if(Arr::first($allKeys, fn($key) => Str::startsWith($key, "$name.*."))) {
|
|
|
|
|
|
+ if (Arr::first($allKeys, fn($key) => Str::startsWith($key, "$name.*."))) {
|
|
$details['type'] = 'object[]';
|
|
$details['type'] = 'object[]';
|
|
unset($details['setter']);
|
|
unset($details['setter']);
|
|
} else if ($childKey = Arr::first($allKeys, fn($key) => Str::startsWith($key, "$name.*"))) {
|
|
} else if ($childKey = Arr::first($allKeys, fn($key) => Str::startsWith($key, "$name.*"))) {
|
|
@@ -856,4 +860,16 @@ trait ParsesValidationRules
|
|
{
|
|
{
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ protected function warnAboutMissingCustomParameterData(string $parameter, array $customParameterData): array
|
|
|
|
+ {
|
|
|
|
+ $parameterPlusDot = $parameter . '.';
|
|
|
|
+ if (count($customParameterData) && !isset($customParameterData[$parameter])
|
|
|
|
+ && !Arr::first(array_keys($customParameterData), fn($key) => str_starts_with($key, $parameterPlusDot))
|
|
|
|
+ ) {
|
|
|
|
+ c::debug($this->getMissingCustomDataMessage($parameter));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $customParameterData;
|
|
|
|
+ }
|
|
}
|
|
}
|