Skip to content

Commit

Permalink
Update approach to relative rule generation to use config
Browse files Browse the repository at this point in the history
  • Loading branch information
bentleyo committed Dec 3, 2022
1 parent 3d10808 commit dffc46e
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 17 deletions.
10 changes: 10 additions & 0 deletions config/data.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@
*/
'wrap' => null,

/**
* Rules can be generated using data from the root request level or relative to
* each data object. When relative rule generation is selected collections of data
* objects will be iterated over and rules generated for each item.
* This allows for more dynamic validation, but has the downside of duplicate
* rule generation.
* For example, items.0.string, items.1.string etc. instead of, items.*.string
*/
'relative_rule_generation' => false,

/**
* Adds a specific caster to the Symphony VarDumper component which hides
* some properties from data objects and collections when being dumped
Expand Down
18 changes: 18 additions & 0 deletions src/Resolvers/DataPropertyValidationRulesResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,24 @@ protected function getNestedRules(
$inferrer->handle($property, $toplevelRules);
}

if (! $this->dataConfig->usesRelativeRuleGeneration()) {
$prefix = match (true) {
$property->type->isDataObject => "{$propertyName}",
$property->type->isDataCollectable => "{$propertyName}.*",
};

return $this->dataValidationRulesResolver
->execute(
$property->type->dataClass,
$payload,
$this->isNestedDataNullable($nullable, $property)
)
->mapWithKeys(fn (array $rules, string $name) => [
"{$prefix}.{$name}" => $rules,
])
->prepend($toplevelRules->normalize(), $propertyName);
}

if ($property->type->isDataCollectable) {
return collect($payload[$propertyName] ?? [])
->flatMap(function ($item, $key) use ($nullable, $property, $propertyName) {
Expand Down
9 changes: 9 additions & 0 deletions src/Support/DataConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class DataConfig
/** @var \Spatie\LaravelData\RuleInferrers\RuleInferrer[] */
protected array $ruleInferrers;

protected bool $relativeRuleGeneration;

public function __construct(array $config)
{
$this->ruleInferrers = array_map(
Expand All @@ -34,6 +36,8 @@ public function __construct(array $config)
foreach ($config['casts'] ?? [] as $castable => $cast) {
$this->casts[ltrim($castable, ' \\')] = app($cast);
}

$this->relativeRuleGeneration = $config['relative_rule_generation'];
}

public function getDataClass(string $class): DataClass
Expand Down Expand Up @@ -81,4 +85,9 @@ public function getRuleInferrers(): array
{
return $this->ruleInferrers;
}

public function usesRelativeRuleGeneration(): bool
{
return $this->relativeRuleGeneration;
}
}
2 changes: 1 addition & 1 deletion tests/RequestDataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ function performRequest(string $string): TestResponse
],
])
->assertStatus(422)
->assertJsonValidationErrors(['simple_collection.1.string' => 'The simple collection.1.string must be a string.']);
->assertJsonValidationErrors(['simple_collection.1.string' => 'The simple_collection.1.string must be a string.']);
});

it('can check for authorization', function () {
Expand Down
2 changes: 1 addition & 1 deletion tests/Resolvers/DataClassValidationRulesResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public static function rules(): array
});

it(
'can overwrite fules for the base collection object which will not affect the collected data object rules',
'can overwrite rules for the base collection object which will not affect the collected data object rules',
function () {
$dataClass = new class () extends Data {
#[DataCollectionOf(SimpleData::class)]
Expand Down
14 changes: 7 additions & 7 deletions tests/Resolvers/DataPropertyValidationRulesResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ function resolveRules(object $class, array $payload = []): array

expect($rules)->toMatchArray([
'property' => ['present', 'array'],
'property.0.string' => ['string', 'required'],
'property.*.string' => ['string', 'required'],
]);

$rules = resolveRules(new class () {
Expand All @@ -172,7 +172,7 @@ function resolveRules(object $class, array $payload = []): array

expect($rules)->toMatchArray([
'property' => ['nullable', 'array'],
'property.0.string' => ['string', 'required'],
'property.*.string' => ['string', 'required'],
]);

$rules = resolveRules(new class () {
Expand All @@ -184,7 +184,7 @@ function resolveRules(object $class, array $payload = []): array

expect($rules)->toMatchArray([
'property' => ['sometimes', 'array'],
'property.0.string' => ['string', 'required'],
'property.*.string' => ['string', 'required'],
]);

$rules = resolveRules(new class () {
Expand All @@ -196,7 +196,7 @@ function resolveRules(object $class, array $payload = []): array

expect($rules)->toMatchArray([
'property' => ['nullable', 'sometimes', 'array'],
'property.0.string' => ['string', 'required'],
'property.*.string' => ['string', 'required'],
]);
});

Expand Down Expand Up @@ -297,7 +297,7 @@ function resolveRules(object $class, array $payload = []): array

expect($rules)->toMatchArray([
'other' => ['present', 'array'],
'other.0.string' => ['string', 'required'],
'other.*.string' => ['string', 'required'],
]);

$rules = resolveRules(new class () {
Expand All @@ -315,7 +315,7 @@ function resolveRules(object $class, array $payload = []): array
'other.data_cased_property' => ['required', 'array'],
'other.data_cased_property.string' => ['string', 'required'],
'other.data_collection_cased_property' => ['present', 'array'],
'other.data_collection_cased_property.0.string' => ['string', 'required'],
'other.data_collection_cased_property.*.string' => ['string', 'required'],
]);
});

Expand Down Expand Up @@ -362,7 +362,7 @@ function () {

expect(resolveRules($data, ['property' => [[]]]))->toMatchArray([
'property' => ['present', 'array', 'size:10'],
'property.0.string' => ['string', 'required'],
'property.*.string' => ['string', 'required'],
]);
}
);
20 changes: 12 additions & 8 deletions tests/ValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ class NestedClassG extends Data {
])
->assertRules([
'collection' => ['present', 'array'],
'collection.0.string' => ['string', 'required'],
'collection.*.string' => ['string', 'required'],
], [
'collection' => [[]],
]);
Expand Down Expand Up @@ -517,7 +517,7 @@ class NestedClassG extends Data {
])
->assertRules([
'collection' => ['nullable', 'array'],
'collection.0.string' => ['string', 'required'],
'collection.*.string' => ['string', 'required'],
], [
'collection' => [[]],
]);
Expand Down Expand Up @@ -546,7 +546,7 @@ class NestedClassG extends Data {
])
->assertRules([
'collection' => ['sometimes', 'array'],
'collection.0.string' => ['string', 'required'],
'collection.*.string' => ['string', 'required'],
], [
'collection' => [[]],
]);
Expand Down Expand Up @@ -583,7 +583,7 @@ public static function rules(): array
DataValidationAsserter::for($dataClass)
->assertRules([
'collection' => ['present', 'array', 'min:10'],
'collection.0.email' => ['string', 'required', 'email:rfc'],
'collection.*.email' => ['string', 'required', 'email:rfc'],
], [
'collection' => [[]],
]);
Expand Down Expand Up @@ -614,13 +614,13 @@ class CollectionClassA extends Data {
DataValidationAsserter::for($dataClass)
->assertRules([
'collection' => ['present', 'array'],
'collection.0.nested' => ['required', 'array'],
'collection.0.nested.string' => ['required', 'string'],
'collection.*.nested' => ['required', 'array'],
'collection.*.nested.string' => ['required', 'string'],
], $payload)
->assertOk($payload);
});

it('can nest data with relative payload', function () {
it('can nest data with payload using relative rule generation', function () {
eval(<<<'PHP'
use Spatie\LaravelData\Data;
class NestedClassH extends Data {
Expand All @@ -644,6 +644,8 @@ class CollectionClassH extends Data {
}
PHP);

config()->set('data.relative_rule_generation', true);

$dataClass = new class () extends Data {
#[DataCollectionOf(\CollectionClassH::class)]
public DataCollection $collection;
Expand All @@ -667,7 +669,7 @@ class CollectionClassH extends Data {
->assertOk($payload);
});

it('can nest data in collections with relative payload', function () {
it('can nest data in collections using relative rule generation', function () {
eval(<<<'PHP'
use Spatie\LaravelData\Data;
class NestedClassI extends Data {
Expand All @@ -687,6 +689,8 @@ public static function rules(array $payload): array
}
PHP);

config()->set('data.relative_rule_generation', true);

$dataClass = new class () extends Data {
public \NestedClassI $nested;
};
Expand Down

0 comments on commit dffc46e

Please sign in to comment.