Skip to content

Commit

Permalink
Merge pull request #768 from hydephp/allow-arbitrary-validation-rules…
Browse files Browse the repository at this point in the history
…-to-be-added-to-publication-field-types

Refactor default to publication field type validation rules and allow arbitrary ones to be added
  • Loading branch information
caendesilva authored Dec 22, 2022
2 parents 07c89b3 + 165da49 commit 07b740a
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 172 deletions.
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

0 comments on commit 07b740a

Please sign in to comment.