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

Validation changes for next major release #35

Open
wants to merge 11 commits into
base: next
Choose a base branch
from
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"ext-json": "*",
"illuminate/database": "^11.0",
"illuminate/support": "^11.0",
"laravel-json-api/core": "^5.0"
"laravel-json-api/core": "^5.0",
"laravel-json-api/validation": "^5.0"
},
"require-dev": {
"orchestra/testbench": "^9.0",
Expand Down
4 changes: 2 additions & 2 deletions src/Contracts/FillableToMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Model;
use LaravelJsonApi\Eloquent\Polymorphism\MorphMany;
use LaravelJsonApi\Validation\Fields\IsValidated;

interface FillableToMany extends IsReadOnly
interface FillableToMany extends IsReadOnly, IsValidated
{

/**
* Fill the model with the value of the JSON:API to-many relation.
*
Expand Down
4 changes: 2 additions & 2 deletions src/Contracts/FillableToOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
namespace LaravelJsonApi\Eloquent\Contracts;

use Illuminate\Database\Eloquent\Model;
use LaravelJsonApi\Validation\Fields\IsValidated;

interface FillableToOne extends IsReadOnly
interface FillableToOne extends IsReadOnly, IsValidated
{

/**
* Does the model need to exist in the database before the relation is filled?
*
Expand Down
4 changes: 2 additions & 2 deletions src/Contracts/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@

use Illuminate\Database\Eloquent\Builder;
use LaravelJsonApi\Contracts\Schema\Filter as BaseFilter;
use LaravelJsonApi\Validation\Filters\IsValidated;

interface Filter extends BaseFilter
interface Filter extends BaseFilter, IsValidated
{

/**
* Does the filter return a singular resource?
*
Expand Down
38 changes: 34 additions & 4 deletions src/Fields/ArrayHash.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
use Closure;
use LaravelJsonApi\Core\Json\Hash;
use LaravelJsonApi\Core\Support\Arr;
use function is_null;
use LaravelJsonApi\Validation\Fields\IsValidated;
use LaravelJsonApi\Validation\Fields\ValidatedWithArrayKeys;
use LaravelJsonApi\Validation\Rules\JsonObject;

class ArrayHash extends Attribute
class ArrayHash extends Attribute implements IsValidated
{
use ValidatedWithArrayKeys;

/**
* @var Closure|null
Expand Down Expand Up @@ -48,6 +51,13 @@ class ArrayHash extends Attribute
*/
private ?string $keyCase = null;

/**
* Whether an empty array is allowed as the value.
*
* @var bool
*/
private bool $allowEmpty = false;

/**
* Create an array attribute.
*
Expand Down Expand Up @@ -184,6 +194,19 @@ public function dasherizeKeys(): self
return $this;
}

/**
* Whether an empty array is allowed as the value.
*
* @param bool $allowEmpty
* @return self
*/
public function allowEmpty(bool $allowEmpty = true): self
{
$this->allowEmpty = $allowEmpty;

return $this;
}

/**
* @inheritDoc
*/
Expand All @@ -208,7 +231,7 @@ protected function deserialize($value)
$value = ($this->keys)($value);
}

if (is_null($value)) {
if ($value === null) {
return null;
}

Expand All @@ -224,12 +247,19 @@ protected function deserialize($value)
*/
protected function assertValue($value): void
{
if ((!is_null($value) && !is_array($value)) || (!empty($value) && !Arr::isAssoc($value))) {
if (($value !== null && !is_array($value)) || (!empty($value) && !Arr::isAssoc($value))) {
throw new \UnexpectedValueException(sprintf(
'Expecting the value of attribute %s to be an associative array.',
$this->name()
));
}
}

/**
* @return array<string, mixed>
*/
protected function defaultRules(): array
{
return ['.' => (new JsonObject())->allowEmpty($this->allowEmpty)];
}
}
16 changes: 13 additions & 3 deletions src/Fields/ArrayList.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
namespace LaravelJsonApi\Eloquent\Fields;

use Illuminate\Support\Arr;
use function is_null;
use LaravelJsonApi\Validation\Fields\IsValidated;
use LaravelJsonApi\Validation\Fields\ValidatedWithArrayKeys;
use LaravelJsonApi\Validation\Rules\JsonArray;
use function sort;

class ArrayList extends Attribute
class ArrayList extends Attribute implements IsValidated
{
use ValidatedWithArrayKeys;

/**
* @var bool
Expand Down Expand Up @@ -80,12 +83,19 @@ protected function deserialize($value)
*/
protected function assertValue($value): void
{
if ((!is_null($value) && !is_array($value)) || (!empty($value) && Arr::isAssoc($value))) {
if (($value !== null && !is_array($value)) || (!empty($value) && Arr::isAssoc($value))) {
throw new \UnexpectedValueException(sprintf(
'Expecting the value of attribute %s to be an array list.',
$this->name()
));
}
}

/**
* @return array<string, mixed>
*/
protected function defaultRules(): array
{
return ['.' => new JsonArray()];
}
}
17 changes: 15 additions & 2 deletions src/Fields/Boolean.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@

namespace LaravelJsonApi\Eloquent\Fields;

class Boolean extends Attribute
use LaravelJsonApi\Validation\Fields\IsValidated;
use LaravelJsonApi\Validation\Fields\ValidatedWithRules;
use LaravelJsonApi\Validation\Rules\JsonBoolean;

class Boolean extends Attribute implements IsValidated
{
use ValidatedWithRules;

/**
* Create a boolean attribute.
Expand All @@ -26,12 +31,20 @@ public static function make(string $fieldName, string $column = null): self
return new self($fieldName, $column);
}

/**
* @return array
*/
protected function defaultRules(): array
{
return [new JsonBoolean()];
}

/**
* @inheritDoc
*/
protected function assertValue($value): void
{
if (!is_null($value) && !is_bool($value)) {
if ($value !== null && !is_bool($value)) {
throw new \UnexpectedValueException(sprintf(
'Expecting the value of attribute %s to be a boolean.',
$this->name()
Expand Down
13 changes: 4 additions & 9 deletions src/Fields/Concerns/IsReadOnly.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,21 @@

trait IsReadOnly
{

/**
* Whether the field is read-only.
*
* @var Closure|bool
*/
private $readOnly = false;
private Closure|bool $readOnly = false;

/**
* Mark the field as read-only.
*
* @param Closure|bool $callback
* @return $this
*/
public function readOnly($callback = true): self
public function readOnly(Closure|bool $callback = true): static
{
if (!is_bool($callback) && !$callback instanceof Closure) {
throw new InvalidArgumentException('Expecting a boolean or closure.');
}

$this->readOnly = $callback;

return $this;
Expand All @@ -47,7 +42,7 @@ public function readOnly($callback = true): self
*
* @return $this
*/
public function readOnlyOnCreate(): self
public function readOnlyOnCreate(): static
{
$this->readOnly(static fn($request) => $request && $request->isMethod('POST'));

Expand All @@ -59,7 +54,7 @@ public function readOnlyOnCreate(): self
*
* @return $this
*/
public function readOnlyOnUpdate(): self
public function readOnlyOnUpdate(): static
{
$this->readOnly(static fn($request) => $request && $request->isMethod('PATCH'));

Expand Down
14 changes: 13 additions & 1 deletion src/Fields/DateTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@

use Carbon\CarbonInterface;
use Illuminate\Support\Facades\Date;
use LaravelJsonApi\Validation\Fields\IsValidated;
use LaravelJsonApi\Validation\Fields\ValidatedWithRules;
use LaravelJsonApi\Validation\Rules\DateTimeIso8601;
use function config;

class DateTime extends Attribute
class DateTime extends Attribute implements IsValidated
{
use ValidatedWithRules;

/**
* Should dates be converted to the defined time zone?
Expand Down Expand Up @@ -113,6 +117,14 @@ protected function parse($value): ?CarbonInterface
return $value;
}

/**
* @return DateTimeIso8601[]
*/
protected function defaultRules(): array
{
return [new DateTimeIso8601()];
}

/**
* @inheritDoc
*/
Expand Down
41 changes: 39 additions & 2 deletions src/Fields/ID.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@
namespace LaravelJsonApi\Eloquent\Fields;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use LaravelJsonApi\Contracts\Schema\ID as IDContract;
use LaravelJsonApi\Core\Schema\Concerns\ClientIds;
use LaravelJsonApi\Core\Schema\Concerns\MatchesIds;
use LaravelJsonApi\Core\Schema\Concerns\Sortable;
use LaravelJsonApi\Eloquent\Contracts\Fillable;
use LaravelJsonApi\Validation\Fields\IsValidated;
use LaravelJsonApi\Validation\Rules\ClientId;

class ID implements IDContract, Fillable
class ID implements IDContract, Fillable, IsValidated
{

use ClientIds;
use MatchesIds;
use Sortable;
Expand All @@ -30,6 +32,11 @@ class ID implements IDContract, Fillable
*/
private ?string $column;

/**
* @var string
*/
private string $validationModifier = 'required';

/**
* Create an id field.
*
Expand Down Expand Up @@ -104,6 +111,36 @@ public function isNotReadOnly($request): bool
return !$this->isReadOnly($request);
}

/**
* @return $this
*/
public function nullable(): self
{
$this->validationModifier = 'nullable';

return $this;
}

/**
* @inheritDoc
*/
public function rulesForCreation(?Request $request): array|null
{
if ($this->acceptsClientIds()) {
return [$this->validationModifier, new ClientId($this)];
}

return null;
}

/**
* @inheritDoc
*/
public function rulesForUpdate(?Request $request, object $model): ?array
{
return null;
}

/**
* @inheritDoc
*/
Expand Down
Loading