Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
shalvah committed Feb 7, 2023
2 parents 8968e0b + 7f134a4 commit 5818922
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 18 deletions.
71 changes: 53 additions & 18 deletions src/Extracting/ParsesValidationRules.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\ClosureValidationRule;
use Knuckles\Scribe\Exceptions\CouldntProcessValidationRule;
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
Expand Down Expand Up @@ -164,11 +166,56 @@ protected function normaliseRules(array $rules): array
*/
protected function parseRule($rule, array &$parameterData, bool $independentOnly, array $allParameters = []): bool
{
try {
if (!(is_string($rule) || $rule instanceof Rule)) {
return true;
// 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;
}

return true;
}

if ($rule instanceof Rule) {
if (method_exists($rule, 'docs')) {
$customData = call_user_func_array([$rule, 'docs'], []) ?: [];

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'];
}

$parameterData = array_merge($parameterData, Arr::except($customData, [
'description', 'example', 'setter',
]));
}

return true;
}

if (!is_string($rule)) {
return false;
}

try {
// Convert string rules into rule + arguments (eg "in:1,2" becomes ["in", ["1", "2"]])
$parsedRule = $this->parseStringRuleIntoRuleAndArguments($rule);
[$rule, $arguments] = $parsedRule;
Expand All @@ -178,12 +225,6 @@ protected function parseRule($rule, array &$parameterData, bool $independentOnly
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;
Expand All @@ -196,7 +237,7 @@ protected function parseRule($rule, array &$parameterData, bool $independentOnly
break;

/*
* Primitive types. No description should be added
* Primitive types. No description should be added
*/
case 'bool':
case 'boolean':
Expand Down Expand Up @@ -453,11 +494,11 @@ protected function parseRule($rule, array &$parameterData, bool $independentOnly
// Other rules not supported
break;
}

return true;
} catch (Throwable $e) {
throw CouldntProcessValidationRule::forParam($parameterData['name'], $rule, $e);
}

return true;
}

/**
Expand All @@ -474,12 +515,6 @@ protected function parseStringRuleIntoRuleAndArguments($rule): array
{
$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);

Expand Down
42 changes: 42 additions & 0 deletions tests/Strategies/GetFromFormRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,27 @@ public function allows_customisation_of_form_request_instantiation()
Globals::$__instantiateFormRequestUsing = null;
}

/** @test */
public function custom_rule_example_doesnt_override_form_request_example()
{
$strategy = new BodyParameters\GetFromFormRequest(new DocumentationConfig([]));
$parametersFromFormRequest = $strategy->getParametersFromValidationRules(
[
'dummy' => ['required', new DummyValidationRule],
],
[
'dummy' => [
'description' => 'New description.',
'example' => 'Overrided example.',
],
],
);

$parsed = $strategy->normaliseArrayAndObjectParameters($parametersFromFormRequest);
$this->assertEquals('Overrided example.', $parsed['dummy']['example']);
$this->assertEquals('New description. This is a dummy test rule.', $parsed['dummy']['description']);
}

protected function fetchViaBodyParams(\ReflectionMethod $method): array
{
$strategy = new BodyParameters\GetFromFormRequest(new DocumentationConfig([]));
Expand All @@ -254,3 +275,24 @@ protected function fetchViaQueryParams(\ReflectionMethod $method): array
return $strategy->getParametersFromFormRequest($method, $route);
}
}

class DummyValidationRule implements \Illuminate\Contracts\Validation\Rule
{
public function passes($attribute, $value)
{
return true;
}

public function message()
{
return '.';
}

public static function docs()
{
return [
'description' => 'This is a dummy test rule.',
'example' => 'Default example, only added if none other give.',
];
}
}
76 changes: 76 additions & 0 deletions tests/Unit/ValidationRuleParsingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,61 @@ public function child_does_not_overwrite_parent_status()
$this->assertCount(2, $results);
$this->assertEquals(true, $results['array_param']['required']);
}

/** @test */
public function can_parse_custom_closure_rules()
{
// Single line DocComment
$ruleset = [
'closure' => [
'bail', 'required',
/** This is a single line parsed closure rule. */
function ($attribute, $value, $fail) {
$fail('Always fail.');
},
],
];

$results = $this->strategy->parse($ruleset);
$this->assertEquals(
'This is a single line parsed closure rule.',
$results['closure']['description']
);

// Block DocComment
$ruleset = [
'closure' => [
'bail', 'required',
/**
* This is a block DocComment
* parsed on a closure rule.
* Extra info.
*/
function ($attribute, $value, $fail) {
$fail('Always fail.');
},
],
];

$results = $this->strategy->parse($ruleset);
$this->assertEquals(
'This is a block DocComment parsed on a closure rule. Extra info.',
$results['closure']['description']
);
}

/** @test */
public function can_parse_custom_rule_classes()
{
$ruleset = [
// The page number. Example: 1
'custom_rule' => ['bail', 'required', new DummyWithDocsValidationRule],
];

$results = $this->strategy->parse($ruleset);
$this->assertEquals(true, $results['custom_rule']['required']);
$this->assertEquals('This is a dummy test rule.', $results['custom_rule']['description']);
}
}

class DummyValidationRule implements \Illuminate\Contracts\Validation\Rule
Expand All @@ -465,3 +520,24 @@ public function message()
return '.';
}
}

class DummyWithDocsValidationRule implements \Illuminate\Contracts\Validation\Rule
{
public function passes($attribute, $value)
{
return true;
}

public function message()
{
return '.';
}

public static function docs()
{
return [
'description' => 'This is a dummy test rule.',
'example' => 'Default example, only added if none other give.',
];
}
}

0 comments on commit 5818922

Please sign in to comment.