Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor default to publication field type validation rules and allow arbitrary ones to be added #768

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d4487ba
Apply fixes from StyleCI
StyleCIBot Dec 21, 2022
1e347da
Add $rules array to the publication field class
caendesilva Dec 21, 2022
d8494fb
Merge branch 'publications-feature' into allow-arbitrary-validation-r…
caendesilva Dec 21, 2022
113b297
Add the rules array to the constructor parameters
caendesilva Dec 21, 2022
d57bf42
Add validation rules to the array representation
caendesilva Dec 21, 2022
fb968f6
Update test link annotation
caendesilva Dec 21, 2022
bc33920
Rename local variable
caendesilva Dec 21, 2022
1d93c2a
Apply fixes from StyleCI
StyleCIBot Dec 21, 2022
0497261
Actually get the default field rules
caendesilva Dec 21, 2022
acfe602
Group together related tests
caendesilva Dec 21, 2022
10f31cb
Return empty array instead of throwing exception for default validati…
caendesilva Dec 21, 2022
f32cdda
Remove 'required' from default types validation rules
caendesilva Dec 21, 2022
9cf859e
Add todo
caendesilva Dec 21, 2022
5261fe5
Join comma-separated values into a single line
caendesilva Dec 21, 2022
d778165
Remove 'between' from default types validation rules
caendesilva Dec 21, 2022
2e48fe2
Change default validation rule to date as datetime is not a valid rule
caendesilva Dec 21, 2022
a90a010
Expect rules to contain the default rules
caendesilva Dec 21, 2022
55f105d
Remove duplicated rules added by the default rules
caendesilva Dec 21, 2022
de85904
Merge duplicate switch branches
caendesilva Dec 21, 2022
b34593e
Shift default value location to enum to only use dynamic rules in switch
caendesilva Dec 21, 2022
16514e5
Add code comment
caendesilva Dec 21, 2022
1882f4b
Remove unnecessary switch cases
caendesilva Dec 21, 2022
9fe9e1a
Apply fixes from StyleCI
StyleCIBot Dec 21, 2022
49c6451
Add 'numeric' rule to integer type
caendesilva Dec 21, 2022
fe835ea
Merge branch 'allow-arbitrary-validation-rules-to-be-added-to-publica…
caendesilva Dec 21, 2022
b58d657
Expect integer to be numeric
caendesilva Dec 21, 2022
702d161
Update PublicationFieldTypesEnumTest.php
caendesilva Dec 21, 2022
bb7e309
Test all default rules to guard against unintentional BC breaks
caendesilva Dec 21, 2022
82063e4
Update test method name
caendesilva Dec 21, 2022
15d0649
Expect default rule
caendesilva Dec 21, 2022
5c3ae0e
Merge unit test into feature test
caendesilva Dec 21, 2022
d046b47
Add base test for the validate method
caendesilva Dec 22, 2022
19b5b96
Test with custom validation rules
caendesilva Dec 22, 2022
dbfaa91
Replace if with ??=
caendesilva Dec 22, 2022
6fba7e5
Apply fixes from StyleCI
StyleCIBot Dec 22, 2022
c4ffb98
Revert "Replace if with ??="
caendesilva Dec 22, 2022
fd65b41
Use strict comparison
caendesilva Dec 22, 2022
9510bdb
Support both arrays and Arrayables to be passed for custom rules
caendesilva Dec 22, 2022
b152eda
Merge branch 'allow-arbitrary-validation-rules-to-be-added-to-publica…
caendesilva Dec 22, 2022
20f5d49
Apply fixes from StyleCI
StyleCIBot Dec 22, 2022
f1e0573
Merge into 'elseif'
caendesilva Dec 22, 2022
6325085
Refactor method
caendesilva Dec 22, 2022
db56881
Use generic parameter for helper method
caendesilva Dec 22, 2022
03e6e64
Introduce local variable
caendesilva Dec 22, 2022
362db37
Apply fixes from StyleCI
StyleCIBot Dec 22, 2022
26b1e88
Apply the custom field rules
caendesilva Dec 22, 2022
ff820a8
Use built in push helper instead of foreach loop
caendesilva Dec 22, 2022
a18f00d
Add baseline test
caendesilva Dec 22, 2022
68f09c6
Add testing helper to merge data
caendesilva Dec 22, 2022
087b922
Test custom field rules can be deserialized for publication type schemas
caendesilva Dec 22, 2022
165da49
Apply fixes from StyleCI
StyleCIBot Dec 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Hyde\Framework\Features\Publications\PublicationService;
use Hyde\Support\Concerns\Serializable;
use Hyde\Support\Contracts\SerializableContract;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Str;
use Rgasch\Collection\Collection;
use function strtolower;
Expand All @@ -27,17 +28,19 @@ class PublicationField implements SerializableContract
public readonly PublicationFieldTypes $type;
public readonly string $name;
public readonly ?string $tagGroup;
public readonly array $rules;

public static function fromArray(array $array): static
{
return new static(...$array);
}

public function __construct(PublicationFieldTypes|string $type, string $name, ?string $tagGroup = null)
public function __construct(PublicationFieldTypes|string $type, string $name, ?string $tagGroup = null, array $rules = [])
{
$this->type = $type instanceof PublicationFieldTypes ? $type : PublicationFieldTypes::from(strtolower($type));
$this->name = Str::kebab($name);
$this->tagGroup = $tagGroup;
$this->rules = $rules;
}

public function toArray(): array
Expand All @@ -46,35 +49,26 @@ public function toArray(): array
'type' => $this->type->value,
'name' => $this->name,
'tagGroup' => $this->tagGroup,
'rules' => $this->rules,
]);
}

/**
* @param \Hyde\Framework\Features\Publications\Models\PublicationType|null $publicationType Required only when using the 'image' type.
*
* @see \Hyde\Framework\Testing\Unit\PublicationFieldTypeValidationRulesTest
* @see https://laravel.com/docs/9.x/validation#available-validation-rules
*/
public function getValidationRules(?PublicationType $publicationType = null): Collection
{
$defaultRules = Collection::create(PublicationFieldTypes::values());
$fieldRules = Collection::create($defaultRules->get($this->type->value));
$fieldRules = Collection::create(PublicationFieldTypes::getRules($this->type));

switch ($this->type->value) {
case 'array':
$fieldRules->add('array');
break;
case 'datetime':
$fieldRules->add('date');
// Here we could check for a "strict" mode type of thing and add 'required' to the rules if we wanted to.

break;
case 'float':
$fieldRules->add('numeric');
break;
case 'integer':
case 'string':
case 'text':
break;
// Apply any field rules.
$fieldRules->push(...$this->rules);

// Apply any dynamic rules.
switch ($this->type->value) {
case 'image':
$mediaFiles = PublicationService::getMediaForPubType($publicationType);
$valueList = $mediaFiles->implode(',');
Expand All @@ -85,23 +79,21 @@ public function getValidationRules(?PublicationType $publicationType = null): Co
$valueList = $tagValues->implode(',');
$fieldRules->add("in:$valueList");
break;
case 'url':
$fieldRules->add('url');
break;
}

return $fieldRules;
}

/** @param \Hyde\Framework\Features\Publications\Models\PublicationType|null $publicationType Required only when using the 'image' type. */
public function validate(mixed $input = null, Collection $fieldRules = null, ?PublicationType $publicationType = null): array
public function validate(mixed $input = null, Arrayable|array|null $fieldRules = null, ?PublicationType $publicationType = null): array
{
if (! $fieldRules) {
$fieldRules = $this->getValidationRules($publicationType);
}
$rules = $this->evaluateArrayable($fieldRules ?? $this->getValidationRules($publicationType));

$validator = validator([$this->name => $input], [$this->name => $fieldRules->toArray()]);
return validator([$this->name => $input], [$this->name => $rules])->validate();
}

return $validator->validate();
protected function evaluateArrayable(array|Arrayable $array): array
{
return $array instanceof Arrayable ? $array->toArray() : $array;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Hyde\Framework\Features\Publications;

use BadMethodCallException;
use Illuminate\Support\Collection;

/**
Expand Down Expand Up @@ -45,16 +44,16 @@ public static function getRules(self $type): array
{
/** @noinspection PhpDuplicateMatchArmBodyInspection */
return match ($type) {
self::String => ['required', 'string', 'between'],
self::Boolean => ['required', 'boolean'],
self::Integer => ['required', 'integer', 'between'],
self::Float => ['required', 'numeric', 'between'],
self::Datetime => ['required', 'datetime', 'between'],
self::Url => ['required', 'url'],
self::Text => ['required', 'string', 'between'],
self::Array => throw new BadMethodCallException('This type has no validation rules'),
self::Image => throw new BadMethodCallException('This type has no validation rules'),
self::Tag => throw new BadMethodCallException('This type has no validation rules'),
self::String => ['string'],
self::Boolean => ['boolean'],
self::Integer => ['integer', 'numeric'],
self::Float => ['numeric'],
self::Datetime => ['date'],
self::Url => ['url'],
self::Text => ['string'],
self::Array => ['array'],
self::Image => [],
self::Tag => [],
};
}
}
154 changes: 148 additions & 6 deletions packages/framework/tests/Feature/PublicationFieldTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
namespace Hyde\Framework\Testing\Feature;

use Hyde\Framework\Features\Publications\Models\PublicationField;
use Hyde\Framework\Features\Publications\Models\PublicationType;
use Hyde\Framework\Features\Publications\PublicationFieldTypes;
use Hyde\Testing\TestCase;
use Illuminate\Validation\ValidationException;
use ValueError;

/**
Expand Down Expand Up @@ -44,23 +46,24 @@ public function test_can_get_field_as_array()
], (new PublicationField('string', 'test'))->toArray());
}

public function test_can_get_field_with_tag_group_as_array()
public function test_can_get_field_with_optional_properties_as_array()
{
$this->assertSame([
'type' => 'string',
'name' => 'test',
'tagGroup' => 'foo',
], (new PublicationField('string', 'test', 'foo'))->toArray());
'rules' => ['required'],
], (new PublicationField('string', 'test', 'foo', ['required']))->toArray());
}

public function test_can_encode_field_as_json()
{
$this->assertSame('{"type":"string","name":"test"}', json_encode(new PublicationField('string', 'test')));
}

public function test_can_encode_field_with_tag_group_as_json()
public function test_can_get_field_with_optional_properties_as_json()
{
$this->assertSame('{"type":"string","name":"test","tagGroup":"foo"}', json_encode(new PublicationField('string', 'test', 'foo')));
$this->assertSame('{"type":"string","name":"test","tagGroup":"foo","rules":["required"]}', json_encode(new PublicationField('string', 'test', 'foo', ['required'])));
}

public function test_can_construct_type_using_enum_case()
Expand Down Expand Up @@ -94,8 +97,147 @@ public function test_name_gets_stored_as_kebab_case()
$this->assertSame('test-field', $field->name);
}

public function test_validate_input_against_rules()
public function testValidate()
{
$this->markTestIncomplete('TODO: Implement this method.');
$validated = (new PublicationField('string', 'myString'))->validate('foo');
$this->assertSame(['my-string' => 'foo'], $validated);

$this->expectValidationException('The my-string must be a string.');
(new PublicationField('string', 'myString'))->validate(1);
}

public function testValidateWithCustomTypeRules()
{
$validated = (new PublicationField('string', 'myString', rules: ['min:3']))->validate('foo');
$this->assertSame(['my-string' => 'foo'], $validated);

$this->expectValidationException('The my-string must be at least 5 characters.');
(new PublicationField('string', 'myString', rules: ['min:5']))->validate('foo');
}

public function testValidateWithCustomRuleCollection()
{
$validated = (new PublicationField('string', 'myString'))->validate('foo', ['min:3']);
$this->assertSame(['my-string' => 'foo'], $validated);

$this->expectValidationException('The my-string must be at least 5 characters.');
(new PublicationField('string', 'myString'))->validate('foo', ['min:5']);
}

public function testValidateWithCustomRuleCollectionOverridesDefaultRules()
{
$this->expectValidationException('The my-string must be a number.');
(new PublicationField('string', 'myString'))->validate('foo', ['numeric']);
}

public function testValidateMethodAcceptsArrayOfRules()
{
$validated = (new PublicationField('string', 'myString'))->validate('foo', ['min:3']);
$this->assertSame(['my-string' => 'foo'], $validated);
}

public function testValidateMethodAcceptsArrayableOfRules()
{
$validated = (new PublicationField('string', 'myString'))->validate('foo', collect(['min:3']));
$this->assertSame(['my-string' => 'foo'], $validated);
}

public function testGetRules()
{
$rules = (new PublicationField('string', 'myString'))->getValidationRules();
$this->assertSame(['string'], $rules->toArray());
}

public function testGetRulesWithCustomTypeRules()
{
$rules = (new PublicationField('string', 'myString', rules: ['foo', 'bar']))->getValidationRules();
$this->assertSame(['string', 'foo', 'bar'], $rules->toArray());
}

public function testGetRulesForArray()
{
$rules = (new PublicationField('array', 'myArray'))->getValidationRules();
$this->assertSame(['array'], $rules->toArray());
}

public function testValidateArrayPasses()
{
$validated = (new PublicationField('array', 'myArray'))->validate(['foo', 'bar', 'baz']);
$this->assertSame(['my-array' => ['foo', 'bar', 'baz']], $validated);
}

public function testValidateArrayFails()
{
$this->expectValidationException('The my-array must be an array.');
(new PublicationField('array', 'myArray'))->validate('foo');
}

public function testGetRulesForDatetime()
{
$rules = (new PublicationField('datetime', 'myDatetime'))->getValidationRules();
$this->assertSame(['date'], $rules->toArray());
}

public function testValidateDatetimePasses()
{
$validated = (new PublicationField('datetime', 'myDatetime'))->validate('2021-01-01');
$this->assertSame(['my-datetime' => '2021-01-01'], $validated);
}

public function testValidateDatetimeFailsForInvalidType()
{
$this->expectValidationException('The my-datetime is not a valid date.');
(new PublicationField('datetime', 'myDatetime'))->validate('string');
}

public function testGetRulesForFloat()
{
$rules = (new PublicationField('float', 'myFloat'))->getValidationRules();
$this->assertSame(['numeric'], $rules->toArray());
}

public function testGetRulesForInteger()
{
$rules = (new PublicationField('integer', 'myInteger'))->getValidationRules();
$this->assertSame(['integer', 'numeric'], $rules->toArray());
}

public function testGetRulesForString()
{
$rules = (new PublicationField('string', 'myString'))->getValidationRules();
$this->assertSame(['string'], $rules->toArray());
}

public function testGetRulesForText()
{
$rules = (new PublicationField('text', 'myText'))->getValidationRules();
$this->assertSame(['string'], $rules->toArray());
}

public function testGetRulesForImage()
{
$this->directory('_media/foo');
$this->file('_media/foo/bar.jpg');
$this->file('_media/foo/baz.png');
$rules = (new PublicationField('image', 'myImage'))->getValidationRules(publicationType: new PublicationType('foo'));
$this->assertSame(['in:_media/foo/bar.jpg,_media/foo/baz.png'], $rules->toArray());
}

public function testGetRulesForTag()
{
$rules = (new PublicationField('tag', 'myTag', tagGroup: 'foo'))->getValidationRules();
$this->assertSame(['in:'], $rules->toArray());
}

public function testGetRulesForUrl()
{
$rules = (new PublicationField('url', 'myUrl'))->getValidationRules();
$this->assertSame(['url'], $rules->toArray());
}

protected function expectValidationException(string $message): void
{
$this->expectException(ValidationException::class);
$this->expectExceptionMessage($message);
}
}
25 changes: 11 additions & 14 deletions packages/framework/tests/Feature/PublicationFieldTypesEnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Hyde\Framework\Testing\Feature;

use BadMethodCallException;
use Hyde\Framework\Features\Publications\PublicationFieldTypes;
use Hyde\Testing\TestCase;

Expand All @@ -29,13 +28,18 @@ public function testCases()
$this->assertSame('tag', PublicationFieldTypes::Tag->value);
}

public function testCanGetRulesForEnum()
public function testGetRules()
{
$this->assertSame([
'required',
'string',
'between',
], PublicationFieldTypes::String->rules());
$this->assertSame(['string'], PublicationFieldTypes::String->rules());
$this->assertSame(['boolean'], PublicationFieldTypes::Boolean->rules());
$this->assertSame(['integer', 'numeric'], PublicationFieldTypes::Integer->rules());
$this->assertSame(['numeric'], PublicationFieldTypes::Float->rules());
$this->assertSame(['date'], PublicationFieldTypes::Datetime->rules());
$this->assertSame(['url'], PublicationFieldTypes::Url->rules());
$this->assertSame(['string'], PublicationFieldTypes::Text->rules());
$this->assertSame(['array'], PublicationFieldTypes::Array->rules());
$this->assertSame([], PublicationFieldTypes::Image->rules());
$this->assertSame([], PublicationFieldTypes::Tag->rules());
}

public function testCollectCreatesCollectionOfCases()
Expand All @@ -58,11 +62,4 @@ public function testValuesReturnsArrayOfCaseValues()
9 => 'tag',
], PublicationFieldTypes::values());
}

public function testCanGetRulesForEnumWithNoRules()
{
$this->expectException(BadMethodCallException::class);
$this->expectExceptionMessage('This type has no validation rules');
PublicationFieldTypes::Tag->rules();
}
}
Loading