From 02431a302541719d6f980938e2f0f3a012ff75cf Mon Sep 17 00:00:00 2001 From: Christopher Gammie Date: Sun, 11 Jun 2023 17:08:17 +0100 Subject: [PATCH 1/4] working wiring for core v4 --- composer.json | 14 +-- src/Http/Controllers/Actions/FetchOne.php | 41 ++----- src/Http/Controllers/Actions/Store.php | 52 +-------- src/Http/Requests/FormRequest.php | 52 +++++++++ src/Http/Requests/ResourceQuery.php | 12 +- src/Routing/Route.php | 6 +- src/ServiceProvider.php | 32 ++++++ src/Validation/Container.php | 34 ++++++ src/Validation/Factory.php | 104 ++++++++++++++++++ .../dummy/app/JsonApi/V1/Posts/PostQuery.php | 8 ++ .../app/JsonApi/V1/Posts/PostRequest.php | 4 + tests/dummy/tests/Api/V1/Posts/ReadTest.php | 1 - 12 files changed, 268 insertions(+), 92 deletions(-) create mode 100644 src/Validation/Container.php create mode 100644 src/Validation/Factory.php diff --git a/composer.json b/composer.json index f7f9c13..26e7ca9 100644 --- a/composer.json +++ b/composer.json @@ -25,12 +25,12 @@ "require": { "php": "^8.1", "ext-json": "*", - "laravel-json-api/core": "^3.0", - "laravel-json-api/eloquent": "^3.0", - "laravel-json-api/encoder-neomerx": "^3.0", - "laravel-json-api/exceptions": "^2.0", - "laravel-json-api/spec": "^2.0", - "laravel-json-api/validation": "^3.0", + "laravel-json-api/core": "^4.0", + "laravel-json-api/eloquent": "^4.0", + "laravel-json-api/encoder-neomerx": "^4.0", + "laravel-json-api/exceptions": "^3.0", + "laravel-json-api/spec": "^3.0", + "laravel-json-api/validation": "^4.0", "laravel/framework": "^10.0" }, "require-dev": { @@ -65,7 +65,7 @@ ] } }, - "minimum-stability": "stable", + "minimum-stability": "dev", "prefer-stable": true, "config": { "sort-packages": true diff --git a/src/Http/Controllers/Actions/FetchOne.php b/src/Http/Controllers/Actions/FetchOne.php index ed77ef2..ba6fbdc 100644 --- a/src/Http/Controllers/Actions/FetchOne.php +++ b/src/Http/Controllers/Actions/FetchOne.php @@ -20,47 +20,22 @@ namespace LaravelJsonApi\Laravel\Http\Controllers\Actions; use Illuminate\Contracts\Support\Responsable; -use Illuminate\Http\Response; +use LaravelJsonApi\Contracts\Http\Actions\FetchOne as FetchOneContract; use LaravelJsonApi\Contracts\Routing\Route; -use LaravelJsonApi\Contracts\Store\Store as StoreContract; -use LaravelJsonApi\Core\Responses\DataResponse; -use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery; trait FetchOne { - /** - * Fetch zero to one JSON API resource by id. + * Fetch zero to one JSON:API resource by id. * * @param Route $route - * @param StoreContract $store - * @return Responsable|Response + * @param FetchOneContract $action + * @return Responsable */ - public function show(Route $route, StoreContract $store) + public function show(Route $route, FetchOneContract $action): Responsable { - $request = ResourceQuery::queryOne( - $resourceType = $route->resourceType() - ); - - $response = null; - - if (method_exists($this, 'reading')) { - $response = $this->reading($request); - } - - if ($response) { - return $response; - } - - $model = $store - ->queryOne($resourceType, $route->modelOrResourceId()) - ->withRequest($request) - ->first(); - - if (method_exists($this, 'read')) { - $response = $this->read($model, $request); - } - - return $response ?: DataResponse::make($model)->withQueryParameters($request); + return $action + ->withIdOrModel($route->modelOrResourceId()) + ->withHooks($this); } } diff --git a/src/Http/Controllers/Actions/Store.php b/src/Http/Controllers/Actions/Store.php index 3c41e68..f668719 100644 --- a/src/Http/Controllers/Actions/Store.php +++ b/src/Http/Controllers/Actions/Store.php @@ -20,59 +20,19 @@ namespace LaravelJsonApi\Laravel\Http\Controllers\Actions; use Illuminate\Contracts\Support\Responsable; -use Illuminate\Http\Response; -use LaravelJsonApi\Contracts\Routing\Route; -use LaravelJsonApi\Contracts\Store\Store as StoreContract; -use LaravelJsonApi\Core\Responses\DataResponse; -use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery; -use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest; +use LaravelJsonApi\Contracts\Http\Actions\Store as StoreAction; trait Store { - /** * Create a new resource. * - * @param Route $route - * @param StoreContract $store - * @return Responsable|Response + * @param StoreAction $action + * @return Responsable */ - public function store(Route $route, StoreContract $store) + public function store(StoreAction $action): Responsable { - $request = ResourceRequest::forResource( - $resourceType = $route->resourceType() - ); - - $query = ResourceQuery::queryOne($resourceType); - $response = null; - - if (method_exists($this, 'saving')) { - $response = $this->saving(null, $request, $query); - } - - if (!$response && method_exists($this, 'creating')) { - $response = $this->creating($request, $query); - } - - if ($response) { - return $response; - } - - $model = $store - ->create($resourceType) - ->withRequest($query) - ->store($request->validated()); - - if (method_exists($this, 'created')) { - $response = $this->created($model, $request, $query); - } - - if (!$response && method_exists($this, 'saved')) { - $response = $this->saved($model, $request, $query); - } - - return $response ?? DataResponse::make($model) - ->withQueryParameters($query) - ->didCreate(); + return $action + ->withHooks($this); } } diff --git a/src/Http/Requests/FormRequest.php b/src/Http/Requests/FormRequest.php index 8ec53b8..75e67c3 100644 --- a/src/Http/Requests/FormRequest.php +++ b/src/Http/Requests/FormRequest.php @@ -35,6 +35,58 @@ class FormRequest extends BaseFormRequest */ public const JSON_API_MEDIA_TYPE = 'application/vnd.api+json'; + + /** + * Get the validator instance for the request. + * + * @return \Illuminate\Contracts\Validation\Validator + */ + public function makeValidator(array $input): \Illuminate\Contracts\Validation\Validator + { + $factory = $this->container->make(\Illuminate\Contracts\Validation\Factory::class); + + $validator = $this->createDefaultValidatorWithInput($factory, $input); + + if (method_exists($this, 'withValidator')) { + $this->withValidator($validator); + } + + if (method_exists($this, 'after')) { + $validator->after($this->container->call( + $this->after(...), + ['validator' => $validator] + )); + } + + $this->setValidator($validator); + + return $this->validator; + } + + /** + * Create the default validator instance. + * + * @param \Illuminate\Contracts\Validation\Factory $factory + * @return \Illuminate\Contracts\Validation\Validator + */ + protected function createDefaultValidatorWithInput(\Illuminate\Contracts\Validation\Factory $factory, array $input) + { + $rules = method_exists($this, 'rules') ? $this->container->call([$this, 'rules']) : []; + + $validator = $factory->make( + $input, $rules, + $this->messages(), $this->attributes() + )->stopOnFirstFailure($this->stopOnFirstFailure); + + if ($this->isPrecognitive()) { + $validator->setRules( + $this->filterPrecognitiveRules($validator->getRulesWithoutPlaceholders()) + ); + } + + return $validator; + } + /** * @return bool */ diff --git a/src/Http/Requests/ResourceQuery.php b/src/Http/Requests/ResourceQuery.php index 1e94f1c..211cc04 100644 --- a/src/Http/Requests/ResourceQuery.php +++ b/src/Http/Requests/ResourceQuery.php @@ -75,9 +75,9 @@ public static function guessQueryManyUsing(callable $resolver): void * Resolve the request instance when querying many resources. * * @param string $resourceType - * @return QueryParameters|ResourceQuery + * @return ResourceQuery */ - public static function queryMany(string $resourceType): QueryParameters + public static function queryMany(string $resourceType): ResourceQuery { $resolver = self::$queryManyResolver ?: new RequestResolver(RequestResolver::COLLECTION_QUERY); @@ -238,6 +238,14 @@ public function unrecognisedParameters(): array ])->all(); } + /** + * @return array + */ + public function toQuery(): array + { + throw new \RuntimeException('Not implemented.'); + } + /** * Get the model that the request relates to, if the URL has a resource id. * diff --git a/src/Routing/Route.php b/src/Routing/Route.php index 1d27826..7499446 100644 --- a/src/Routing/Route.php +++ b/src/Routing/Route.php @@ -175,9 +175,9 @@ public function schema(): Schema */ public function authorizer(): Authorizer { - return $this->container->make( - $this->schema()->authorizer() - ); + return $this->server + ->authorizers() + ->authorizerFor($this->resourceType()); } /** diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index d827f2b..3881f7d 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -21,9 +21,14 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Foundation\Application; +use Illuminate\Contracts\Pipeline\Pipeline; use Illuminate\Routing\Router; use Illuminate\Support\ServiceProvider as BaseServiceProvider; use LaravelJsonApi\Contracts; +use LaravelJsonApi\Core\Bus\Commands\Dispatcher as CommandDispatcher; +use LaravelJsonApi\Core\Bus\Queries\Dispatcher as QueryDispatcher; +use LaravelJsonApi\Core\Http\Actions\FetchOne; +use LaravelJsonApi\Core\Http\Actions\Store; use LaravelJsonApi\Core\JsonApiService; use LaravelJsonApi\Core\Server\ServerRepository; use LaravelJsonApi\Core\Support\AppResolver; @@ -72,6 +77,13 @@ public function register(): void $this->bindAuthorizer(); $this->bindService(); $this->bindServer(); + $this->bindActionsCommandsAndQueries(); + + /** @TODO wtf? why isn't it working without this? */ + $this->app->bind(Pipeline::class, \Illuminate\Pipeline\Pipeline::class); + + /** @TODO will need to remove this temporary wiring */ + $this->app->bind(Contracts\Validation\Container::class, Validation\Container::class); } /** @@ -134,5 +146,25 @@ private function bindServer(): void $this->app->bind(Contracts\Resources\Container::class, static function (Application $app) { return $app->make(Contracts\Server\Server::class)->resources(); }); + + $this->app->bind(Contracts\Auth\Container::class, static function (Application $app) { + return $app->make(Contracts\Server\Server::class)->authorizers(); + }); + } + + /** + * @return void + */ + private function bindActionsCommandsAndQueries(): void + { + /** Actions */ + $this->app->bind(Contracts\Http\Actions\FetchOne::class, FetchOne::class); + $this->app->bind(Contracts\Http\Actions\Store::class, Store::class); + + /** Commands */ + $this->app->bind(Contracts\Bus\Commands\Dispatcher::class, CommandDispatcher::class); + + /** Queries */ + $this->app->bind(Contracts\Bus\Queries\Dispatcher::class, QueryDispatcher::class); } } diff --git a/src/Validation/Container.php b/src/Validation/Container.php new file mode 100644 index 0000000..3780cb3 --- /dev/null +++ b/src/Validation/Container.php @@ -0,0 +1,34 @@ +type) implements QueryOneValidator { + public function __construct(private readonly ResourceType $type) + { + } + + public function forRequest(Request $request): Validator + { + return $this->make($request, (array) $request->query()); + } + + public function make(?Request $request, array $parameters): Validator + { + try { + $query = ResourceQuery::queryOne($this->type->value); + } catch (\Throwable $ex) { + throw new \RuntimeException('Not expecting resource query to throw.'); + } + + return $query->makeValidator($parameters); + } + }; + } + + /** + * @inheritDoc + */ + public function store(): StoreValidator + { + return new class($this->type) implements StoreValidator { + public function __construct(private readonly ResourceType $type) + { + } + + public function extract(Store $operation): array + { + $resource = ResourceObject::fromArray( + $operation->data->toArray() + ); + + return $resource->all(); + } + + public function make(?Request $request, Store $operation): Validator + { + try { + $resource = ResourceRequest::forResource($this->type->value); + } catch (\Throwable $ex) { + throw new \RuntimeException('Not expecting resource query to throw.'); + } + + return $resource->makeValidator( + $this->extract($operation), + ); + } + }; + } +} diff --git a/tests/dummy/app/JsonApi/V1/Posts/PostQuery.php b/tests/dummy/app/JsonApi/V1/Posts/PostQuery.php index c6fb0dd..f2391be 100644 --- a/tests/dummy/app/JsonApi/V1/Posts/PostQuery.php +++ b/tests/dummy/app/JsonApi/V1/Posts/PostQuery.php @@ -59,4 +59,12 @@ public function rules(): array ], ]; } + + /** + * @return void + */ + public function validateResolved() + { + // no-op + } } diff --git a/tests/dummy/app/JsonApi/V1/Posts/PostRequest.php b/tests/dummy/app/JsonApi/V1/Posts/PostRequest.php index dd131c1..4469482 100644 --- a/tests/dummy/app/JsonApi/V1/Posts/PostRequest.php +++ b/tests/dummy/app/JsonApi/V1/Posts/PostRequest.php @@ -26,6 +26,10 @@ class PostRequest extends ResourceRequest { + public function validateResolved() + { + // no-op + } /** * @return array diff --git a/tests/dummy/tests/Api/V1/Posts/ReadTest.php b/tests/dummy/tests/Api/V1/Posts/ReadTest.php index db8ebaa..598545d 100644 --- a/tests/dummy/tests/Api/V1/Posts/ReadTest.php +++ b/tests/dummy/tests/Api/V1/Posts/ReadTest.php @@ -27,7 +27,6 @@ class ReadTest extends TestCase { - public function test(): void { $post = Post::factory()->create(); From 48d91107d34ced647875178c4bdc4af8d828d506 Mon Sep 17 00:00:00 2001 From: Christopher Gammie Date: Sun, 18 Jun 2023 15:24:52 +0100 Subject: [PATCH 2/4] add wiring for fetch many --- src/Http/Controllers/Actions/FetchMany.php | 45 +-- src/Http/Controllers/Actions/FetchOne.php | 14 +- src/Http/Controllers/Actions/Store.php | 15 +- src/Http/Requests/JsonApiRequest.php | 262 ++++++++++++++++++ src/ServiceProvider.php | 2 + src/Validation/Factory.php | 29 ++ .../JsonApi/V1/Posts/PostCollectionQuery.php | 8 + 7 files changed, 327 insertions(+), 48 deletions(-) create mode 100644 src/Http/Requests/JsonApiRequest.php diff --git a/src/Http/Controllers/Actions/FetchMany.php b/src/Http/Controllers/Actions/FetchMany.php index 9efafde..0c6dcf0 100644 --- a/src/Http/Controllers/Actions/FetchMany.php +++ b/src/Http/Controllers/Actions/FetchMany.php @@ -19,48 +19,23 @@ namespace LaravelJsonApi\Laravel\Http\Controllers\Actions; -use Illuminate\Contracts\Support\Responsable; -use Illuminate\Http\Response; -use LaravelJsonApi\Contracts\Routing\Route; -use LaravelJsonApi\Contracts\Store\Store as StoreContract; +use LaravelJsonApi\Contracts\Http\Actions\FetchMany as FetchManyContract; use LaravelJsonApi\Core\Responses\DataResponse; -use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery; +use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest; trait FetchMany { - /** - * Fetch zero to many JSON API resources. + * Fetch zero-to-many JSON:API resources. * - * @param Route $route - * @param StoreContract $store - * @return Responsable|Response + * @param JsonApiRequest $request + * @param FetchManyContract $action + * @return DataResponse */ - public function index(Route $route, StoreContract $store) + public function index(JsonApiRequest $request, FetchManyContract $action): DataResponse { - $request = ResourceQuery::queryMany( - $resourceType = $route->resourceType() - ); - - $response = null; - - if (method_exists($this, 'searching')) { - $response = $this->searching($request); - } - - if ($response) { - return $response; - } - - $data = $store - ->queryAll($resourceType) - ->withRequest($request) - ->firstOrPaginate($request->page()); - - if (method_exists($this, 'searched')) { - $response = $this->searched($data, $request); - } - - return $response ?: DataResponse::make($data)->withQueryParameters($request); + return $action + ->withHooks($this) + ->execute($request); } } diff --git a/src/Http/Controllers/Actions/FetchOne.php b/src/Http/Controllers/Actions/FetchOne.php index ba6fbdc..743ec57 100644 --- a/src/Http/Controllers/Actions/FetchOne.php +++ b/src/Http/Controllers/Actions/FetchOne.php @@ -19,23 +19,23 @@ namespace LaravelJsonApi\Laravel\Http\Controllers\Actions; -use Illuminate\Contracts\Support\Responsable; use LaravelJsonApi\Contracts\Http\Actions\FetchOne as FetchOneContract; -use LaravelJsonApi\Contracts\Routing\Route; +use LaravelJsonApi\Core\Responses\DataResponse; +use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest; trait FetchOne { /** * Fetch zero to one JSON:API resource by id. * - * @param Route $route + * @param JsonApiRequest $request * @param FetchOneContract $action - * @return Responsable + * @return DataResponse */ - public function show(Route $route, FetchOneContract $action): Responsable + public function show(JsonApiRequest $request, FetchOneContract $action): DataResponse { return $action - ->withIdOrModel($route->modelOrResourceId()) - ->withHooks($this); + ->withHooks($this) + ->execute($request); } } diff --git a/src/Http/Controllers/Actions/Store.php b/src/Http/Controllers/Actions/Store.php index f668719..2693a51 100644 --- a/src/Http/Controllers/Actions/Store.php +++ b/src/Http/Controllers/Actions/Store.php @@ -19,20 +19,23 @@ namespace LaravelJsonApi\Laravel\Http\Controllers\Actions; -use Illuminate\Contracts\Support\Responsable; -use LaravelJsonApi\Contracts\Http\Actions\Store as StoreAction; +use LaravelJsonApi\Contracts\Http\Actions\Store as StoreContract; +use LaravelJsonApi\Core\Responses\DataResponse; +use LaravelJsonApi\Laravel\Http\Requests\JsonApiRequest; trait Store { /** * Create a new resource. * - * @param StoreAction $action - * @return Responsable + * @param JsonApiRequest $request + * @param StoreContract $action + * @return DataResponse */ - public function store(StoreAction $action): Responsable + public function store(JsonApiRequest $request, StoreContract $action): DataResponse { return $action - ->withHooks($this); + ->withHooks($this) + ->execute($request); } } diff --git a/src/Http/Requests/JsonApiRequest.php b/src/Http/Requests/JsonApiRequest.php new file mode 100644 index 0000000..c5118a5 --- /dev/null +++ b/src/Http/Requests/JsonApiRequest.php @@ -0,0 +1,262 @@ +getAcceptableContentTypes(); + + return isset($acceptable[0]) && self::JSON_API_MEDIA_TYPE === $acceptable[0]; + } + + /** + * @return bool + */ + public function acceptsJsonApi(): bool + { + return $this->accepts(self::JSON_API_MEDIA_TYPE); + } + + /** + * Determine if the request is sending JSON API content. + * + * @return bool + */ + public function isJsonApi(): bool + { + return $this->matchesType(self::JSON_API_MEDIA_TYPE, $this->header('CONTENT_TYPE')); + } + + /** + * Is this a request to view any resource? (Index action.) + * + * @return bool + */ + public function isViewingAny(): bool + { + return $this->isMethod('GET') && $this->doesntHaveResourceId() && $this->isNotRelationship(); + } + + /** + * Is this a request to view a specific resource? (Read action.) + * + * @return bool + */ + public function isViewingOne(): bool + { + return $this->isMethod('GET') && $this->hasResourceId() && $this->isNotRelationship(); + } + + /** + * Is this a request to view related resources in a relationship? (Show-related action.) + * + * @return bool + */ + public function isViewingRelated(): bool + { + return $this->isMethod('GET') && $this->isRelationship() && !$this->urlHasRelationships(); + } + + /** + * Is this a request to view resource identifiers in a relationship? (Show-relationship action.) + * + * @return bool + */ + public function isViewingRelationship(): bool + { + return $this->isMethod('GET') && $this->isRelationship() && $this->urlHasRelationships(); + } + + /** + * Is this a request to create a resource? + * + * @return bool + */ + public function isCreating(): bool + { + return $this->isMethod('POST') && $this->isNotRelationship(); + } + + /** + * Is this a request to update a resource? + * + * @return bool + */ + public function isUpdating(): bool + { + return $this->isMethod('PATCH') && $this->isNotRelationship(); + } + + /** + * Is this a request to create or update a resource? + * + * @return bool + */ + public function isCreatingOrUpdating(): bool + { + return $this->isCreating() || $this->isUpdating(); + } + + /** + * Is this a request to replace a resource relationship? + * + * @return bool + */ + public function isUpdatingRelationship(): bool + { + return $this->isMethod('PATCH') && $this->isRelationship(); + } + + /** + * Is this a request to attach records to a resource relationship? + * + * @return bool + */ + public function isAttachingRelationship(): bool + { + return $this->isMethod('POST') && $this->isRelationship(); + } + + /** + * Is this a request to detach records from a resource relationship? + * + * @return bool + */ + public function isDetachingRelationship(): bool + { + return $this->isMethod('DELETE') && $this->isRelationship(); + } + + /** + * Is this a request to modify a resource relationship? + * + * @return bool + */ + public function isModifyingRelationship(): bool + { + return $this->isUpdatingRelationship() || + $this->isAttachingRelationship() || + $this->isDetachingRelationship(); + } + + /** + * @return bool + */ + public function isDeleting(): bool + { + return $this->isMethod('DELETE') && $this->isNotRelationship(); + } + + /** + * Is this a request to view or modify a relationship? + * + * @return bool + */ + public function isRelationship(): bool + { + return $this->jsonApi()->route()->hasRelation(); + } + + /** + * Is this a request to not view a relationship? + * + * @return bool + */ + public function isNotRelationship(): bool + { + return !$this->isRelationship(); + } + + /** + * Get the field name for a relationship request. + * + * @return string|null + */ + public function getFieldName(): ?string + { + $route = $this->jsonApi()->route(); + + if ($route->hasRelation()) { + return $route->fieldName(); + } + + return null; + } + + /** + * @return JsonApiService + */ + final protected function jsonApi(): JsonApiService + { + return $this->container->make(JsonApiService::class); + } + + /** + * Is there a resource id? + * + * @return bool + */ + private function hasResourceId(): bool + { + return $this->jsonApi()->route()->hasResourceId(); + } + + /** + * Is the request not for a specific resource? + * + * @return bool + */ + private function doesntHaveResourceId(): bool + { + return !$this->hasResourceId(); + } + + /** + * Does the URL contain the keyword "relationships". + * + * @return bool + */ + private function urlHasRelationships(): bool + { + return Str::of($this->url())->contains('relationships'); + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 3881f7d..2905363 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -27,6 +27,7 @@ use LaravelJsonApi\Contracts; use LaravelJsonApi\Core\Bus\Commands\Dispatcher as CommandDispatcher; use LaravelJsonApi\Core\Bus\Queries\Dispatcher as QueryDispatcher; +use LaravelJsonApi\Core\Http\Actions\FetchMany; use LaravelJsonApi\Core\Http\Actions\FetchOne; use LaravelJsonApi\Core\Http\Actions\Store; use LaravelJsonApi\Core\JsonApiService; @@ -158,6 +159,7 @@ private function bindServer(): void private function bindActionsCommandsAndQueries(): void { /** Actions */ + $this->app->bind(Contracts\Http\Actions\FetchMany::class, FetchMany::class); $this->app->bind(Contracts\Http\Actions\FetchOne::class, FetchOne::class); $this->app->bind(Contracts\Http\Actions\Store::class, Store::class); diff --git a/src/Validation/Factory.php b/src/Validation/Factory.php index 3bf2484..c8b87e6 100644 --- a/src/Validation/Factory.php +++ b/src/Validation/Factory.php @@ -21,6 +21,7 @@ use Illuminate\Contracts\Validation\Validator; use Illuminate\Http\Request; +use LaravelJsonApi\Contracts\Validation\QueryManyValidator; use LaravelJsonApi\Contracts\Validation\QueryOneValidator; use LaravelJsonApi\Contracts\Validation\StoreValidator; use LaravelJsonApi\Core\Document\Input\Values\ResourceType; @@ -40,6 +41,34 @@ public function __construct(private readonly ResourceType $type) { } + /** + * @return QueryManyValidator + */ + public function queryMany(): QueryManyValidator + { + return new class($this->type) implements QueryManyValidator { + public function __construct(private readonly ResourceType $type) + { + } + + public function forRequest(Request $request): Validator + { + return $this->make($request, (array) $request->query()); + } + + public function make(?Request $request, array $parameters): Validator + { + try { + $query = ResourceQuery::queryMany($this->type->value); + } catch (\Throwable $ex) { + throw new \RuntimeException('Not expecting resource query to throw.'); + } + + return $query->makeValidator($parameters); + } + }; + } + /** * @inheritDoc */ diff --git a/tests/dummy/app/JsonApi/V1/Posts/PostCollectionQuery.php b/tests/dummy/app/JsonApi/V1/Posts/PostCollectionQuery.php index 84b9bf2..5a7e370 100644 --- a/tests/dummy/app/JsonApi/V1/Posts/PostCollectionQuery.php +++ b/tests/dummy/app/JsonApi/V1/Posts/PostCollectionQuery.php @@ -69,4 +69,12 @@ public function rules(): array ], ]; } + + /** + * @return void + */ + public function validateResolved() + { + // no-op + } } From 00ef9848aea05ec4003064b8a50b156d81936bd9 Mon Sep 17 00:00:00 2001 From: Christopher Gammie Date: Sun, 24 Mar 2024 18:56:42 +0000 Subject: [PATCH 3/4] wip: use new validation implementation --- composer.json | 2 +- src/Routing/ResourceRegistrar.php | 6 +- src/ServiceProvider.php | 3 - src/Validation/Container.php | 26 ---- src/Validation/Factory.php | 125 ------------------ .../app/JsonApi/V1/Comments/CommentSchema.php | 10 +- .../app/JsonApi/V1/Images/ImageSchema.php | 10 +- .../app/JsonApi/V1/Phones/PhoneSchema.php | 2 + .../dummy/app/JsonApi/V1/Posts/PostSchema.php | 9 +- tests/dummy/app/JsonApi/V1/Tags/TagSchema.php | 10 +- .../dummy/app/JsonApi/V1/Users/UserSchema.php | 10 +- .../app/JsonApi/V1/Videos/VideoSchema.php | 10 +- tests/lib/Integration/Routing/TestCase.php | 22 ++- 13 files changed, 33 insertions(+), 212 deletions(-) delete mode 100644 src/Validation/Container.php delete mode 100644 src/Validation/Factory.php diff --git a/composer.json b/composer.json index ff4f859..5737128 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "php": "^8.2", "ext-json": "*", "laravel-json-api/core": "^5.0", - "laravel-json-api/eloquent": "^5.0", + "laravel-json-api/eloquent": "dev-feature/validation", "laravel-json-api/encoder-neomerx": "^5.0", "laravel-json-api/exceptions": "^4.0", "laravel-json-api/spec": "^4.0", diff --git a/src/Routing/ResourceRegistrar.php b/src/Routing/ResourceRegistrar.php index 265fc79..1bb778b 100644 --- a/src/Routing/ResourceRegistrar.php +++ b/src/Routing/ResourceRegistrar.php @@ -309,9 +309,9 @@ protected function addResourceDestroy(string $resourceType, string $controller, private function getResourceUri(string $resourceType): string { return $this->server - ->schemas() - ->schemaFor($resourceType) - ->uriType(); + ->statics() + ->schemaForType($resourceType) + ->getUriType(); } /** diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index b86c5c2..65161a4 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -74,9 +74,6 @@ public function register(): void /** @TODO wtf? why isn't it working without this? */ $this->app->bind(Pipeline::class, \Illuminate\Pipeline\Pipeline::class); - - /** @TODO will need to remove this temporary wiring */ - $this->app->bind(Contracts\Validation\Container::class, Validation\Container::class); } /** diff --git a/src/Validation/Container.php b/src/Validation/Container.php deleted file mode 100644 index 92a83a6..0000000 --- a/src/Validation/Container.php +++ /dev/null @@ -1,26 +0,0 @@ -type) implements QueryManyValidator { - public function __construct(private readonly ResourceType $type) - { - } - - public function forRequest(Request $request): Validator - { - return $this->make($request, (array) $request->query()); - } - - public function make(?Request $request, array $parameters): Validator - { - try { - $query = ResourceQuery::queryMany($this->type->value); - } catch (\Throwable $ex) { - throw new \RuntimeException('Not expecting resource query to throw.'); - } - - return $query->makeValidator($parameters); - } - }; - } - - /** - * @inheritDoc - */ - public function queryOne(): QueryOneValidator - { - return new class($this->type) implements QueryOneValidator { - public function __construct(private readonly ResourceType $type) - { - } - - public function forRequest(Request $request): Validator - { - return $this->make($request, (array) $request->query()); - } - - public function make(?Request $request, array $parameters): Validator - { - try { - $query = ResourceQuery::queryOne($this->type->value); - } catch (\Throwable $ex) { - throw new \RuntimeException('Not expecting resource query to throw.'); - } - - return $query->makeValidator($parameters); - } - }; - } - - /** - * @inheritDoc - */ - public function store(): StoreValidator - { - return new class($this->type) implements StoreValidator { - public function __construct(private readonly ResourceType $type) - { - } - - public function extract(Store $operation): array - { - $resource = ResourceObject::fromArray( - $operation->data->toArray() - ); - - return $resource->all(); - } - - public function make(?Request $request, Store $operation): Validator - { - try { - $resource = ResourceRequest::forResource($this->type->value); - } catch (\Throwable $ex) { - throw new \RuntimeException('Not expecting resource query to throw.'); - } - - return $resource->makeValidator( - $this->extract($operation), - ); - } - }; - } -} diff --git a/tests/dummy/app/JsonApi/V1/Comments/CommentSchema.php b/tests/dummy/app/JsonApi/V1/Comments/CommentSchema.php index 6ad6366..361bf0a 100644 --- a/tests/dummy/app/JsonApi/V1/Comments/CommentSchema.php +++ b/tests/dummy/app/JsonApi/V1/Comments/CommentSchema.php @@ -12,6 +12,7 @@ namespace App\JsonApi\V1\Comments; use App\Models\Comment; +use LaravelJsonApi\Core\Schema\Attributes\Model; use LaravelJsonApi\Eloquent\Fields\DateTime; use LaravelJsonApi\Eloquent\Fields\ID; use LaravelJsonApi\Eloquent\Fields\Relations\BelongsTo; @@ -20,16 +21,9 @@ use LaravelJsonApi\Eloquent\Pagination\PagePagination; use LaravelJsonApi\Eloquent\Schema; +#[Model(Comment::class)] class CommentSchema extends Schema { - - /** - * The model the schema corresponds to. - * - * @var string - */ - public static string $model = Comment::class; - /** * @inheritDoc */ diff --git a/tests/dummy/app/JsonApi/V1/Images/ImageSchema.php b/tests/dummy/app/JsonApi/V1/Images/ImageSchema.php index 02aba0e..bb54969 100644 --- a/tests/dummy/app/JsonApi/V1/Images/ImageSchema.php +++ b/tests/dummy/app/JsonApi/V1/Images/ImageSchema.php @@ -12,6 +12,7 @@ namespace App\JsonApi\V1\Images; use App\Models\Image; +use LaravelJsonApi\Core\Schema\Attributes\Model; use LaravelJsonApi\Eloquent\Fields\DateTime; use LaravelJsonApi\Eloquent\Fields\ID; use LaravelJsonApi\Eloquent\Fields\Str; @@ -19,16 +20,9 @@ use LaravelJsonApi\Eloquent\Pagination\PagePagination; use LaravelJsonApi\Eloquent\Schema; +#[Model(Image::class)] class ImageSchema extends Schema { - - /** - * The model the schema corresponds to. - * - * @var string - */ - public static string $model = Image::class; - /** * @inheritDoc */ diff --git a/tests/dummy/app/JsonApi/V1/Phones/PhoneSchema.php b/tests/dummy/app/JsonApi/V1/Phones/PhoneSchema.php index c237ca3..f6c5041 100644 --- a/tests/dummy/app/JsonApi/V1/Phones/PhoneSchema.php +++ b/tests/dummy/app/JsonApi/V1/Phones/PhoneSchema.php @@ -12,11 +12,13 @@ namespace App\JsonApi\V1\Phones; use App\Models\Phone; +use LaravelJsonApi\Core\Schema\Attributes\Model; use LaravelJsonApi\Eloquent\Fields\DateTime; use LaravelJsonApi\Eloquent\Fields\ID; use LaravelJsonApi\Eloquent\Fields\Str; use LaravelJsonApi\Eloquent\Schema; +#[Model(Phone::class)] class PhoneSchema extends Schema { /** diff --git a/tests/dummy/app/JsonApi/V1/Posts/PostSchema.php b/tests/dummy/app/JsonApi/V1/Posts/PostSchema.php index ca9abd6..958b784 100644 --- a/tests/dummy/app/JsonApi/V1/Posts/PostSchema.php +++ b/tests/dummy/app/JsonApi/V1/Posts/PostSchema.php @@ -12,6 +12,7 @@ namespace App\JsonApi\V1\Posts; use App\Models\Post; +use LaravelJsonApi\Core\Schema\Attributes\Model; use LaravelJsonApi\Eloquent\Fields\DateTime; use LaravelJsonApi\Eloquent\Fields\ID; use LaravelJsonApi\Eloquent\Fields\Relations\BelongsTo; @@ -30,17 +31,11 @@ use LaravelJsonApi\Eloquent\SoftDeletes; use LaravelJsonApi\Eloquent\Sorting\SortCountable; +#[Model(Post::class)] class PostSchema extends Schema { use SoftDeletes; - /** - * The model the schema corresponds to. - * - * @var string - */ - public static string $model = Post::class; - /** * The maximum depth of include paths. * diff --git a/tests/dummy/app/JsonApi/V1/Tags/TagSchema.php b/tests/dummy/app/JsonApi/V1/Tags/TagSchema.php index 99a5d89..f9d6eb1 100644 --- a/tests/dummy/app/JsonApi/V1/Tags/TagSchema.php +++ b/tests/dummy/app/JsonApi/V1/Tags/TagSchema.php @@ -12,6 +12,7 @@ namespace App\JsonApi\V1\Tags; use App\Models\Tag; +use LaravelJsonApi\Core\Schema\Attributes\Model; use LaravelJsonApi\Eloquent\Fields\DateTime; use LaravelJsonApi\Eloquent\Fields\ID; use LaravelJsonApi\Eloquent\Fields\Relations\BelongsToMany; @@ -20,16 +21,9 @@ use LaravelJsonApi\Eloquent\Pagination\PagePagination; use LaravelJsonApi\Eloquent\Schema; +#[Model(Tag::class)] class TagSchema extends Schema { - - /** - * The model the schema corresponds to. - * - * @var string - */ - public static string $model = Tag::class; - /** * @inheritDoc */ diff --git a/tests/dummy/app/JsonApi/V1/Users/UserSchema.php b/tests/dummy/app/JsonApi/V1/Users/UserSchema.php index 69436ca..6502b54 100644 --- a/tests/dummy/app/JsonApi/V1/Users/UserSchema.php +++ b/tests/dummy/app/JsonApi/V1/Users/UserSchema.php @@ -12,6 +12,7 @@ namespace App\JsonApi\V1\Users; use App\Models\User; +use LaravelJsonApi\Core\Schema\Attributes\Model; use LaravelJsonApi\Eloquent\Fields\DateTime; use LaravelJsonApi\Eloquent\Fields\ID; use LaravelJsonApi\Eloquent\Fields\Relations\HasOne; @@ -21,16 +22,9 @@ use LaravelJsonApi\Eloquent\Pagination\PagePagination; use LaravelJsonApi\Eloquent\Schema; +#[Model(User::class)] class UserSchema extends Schema { - - /** - * The model the schema corresponds to. - * - * @var string - */ - public static string $model = User::class; - /** * @inheritDoc */ diff --git a/tests/dummy/app/JsonApi/V1/Videos/VideoSchema.php b/tests/dummy/app/JsonApi/V1/Videos/VideoSchema.php index 908fdf1..3b81da0 100644 --- a/tests/dummy/app/JsonApi/V1/Videos/VideoSchema.php +++ b/tests/dummy/app/JsonApi/V1/Videos/VideoSchema.php @@ -12,6 +12,7 @@ namespace App\JsonApi\V1\Videos; use App\Models\Video; +use LaravelJsonApi\Core\Schema\Attributes\Model; use LaravelJsonApi\Eloquent\Fields\DateTime; use LaravelJsonApi\Eloquent\Fields\ID; use LaravelJsonApi\Eloquent\Fields\Relations\BelongsToMany; @@ -20,16 +21,9 @@ use LaravelJsonApi\Eloquent\Pagination\PagePagination; use LaravelJsonApi\Eloquent\Schema; +#[Model(Video::class)] class VideoSchema extends Schema { - - /** - * The model the schema corresponds to. - * - * @var string - */ - public static string $model = Video::class; - /** * @inheritDoc */ diff --git a/tests/lib/Integration/Routing/TestCase.php b/tests/lib/Integration/Routing/TestCase.php index e520ec1..6bf7b63 100644 --- a/tests/lib/Integration/Routing/TestCase.php +++ b/tests/lib/Integration/Routing/TestCase.php @@ -18,6 +18,8 @@ use LaravelJsonApi\Contracts\Schema\ID; use LaravelJsonApi\Contracts\Schema\Relation; use LaravelJsonApi\Contracts\Schema\Schema; +use LaravelJsonApi\Contracts\Schema\StaticSchema\StaticContainer; +use LaravelJsonApi\Contracts\Schema\StaticSchema\StaticSchema; use LaravelJsonApi\Contracts\Server\Repository; use LaravelJsonApi\Contracts\Server\Server; use LaravelJsonApi\Laravel\Tests\Integration\TestCase as BaseTestCase; @@ -45,9 +47,9 @@ protected function setUp(): void /** * @param string $name - * @return Server|MockObject + * @return Server&MockObject */ - protected function createServer(string $name): Server + protected function createServer(string $name): Server&MockObject { $mock = $this->createMock(Server::class); $mock->method('name')->willReturn($name); @@ -57,27 +59,33 @@ protected function createServer(string $name): Server } /** - * @param Server|MockObject $server + * @param Server&MockObject $server * @param string $name * @param string|null $pattern * @param string|null $uriType - * @return Schema|MockObject + * @return Schema&MockObject */ protected function createSchema( - Server $server, + Server&MockObject $server, string $name, string $pattern = null, string $uriType = null - ): Schema + ): Schema&MockObject { + $static = $this->createMock(StaticSchema::class); + $static->method('getUriType')->willReturn($uriType ?: $name); + + $statics = $this->createMock(StaticContainer::class); + $statics->method('schemaForType')->with($name)->willReturn($static); + $schema = $this->createMock(Schema::class); - $schema->method('uriType')->willReturn($uriType ?: $name); $schema->method('id')->willReturn($id = $this->createMock(ID::class)); $id->method('pattern')->willReturn($pattern ?: '[0-9]+'); $schemas = $this->createMock(Container::class); $schemas->method('schemaFor')->with($name)->willReturn($schema); + $server->method('statics')->willReturn($statics); $server->method('schemas')->willReturn($schemas); return $schema; From 056db78297c4400a898ed1cef0e13e2bae3ed9d2 Mon Sep 17 00:00:00 2001 From: Christopher Gammie Date: Wed, 8 May 2024 20:09:23 +0100 Subject: [PATCH 4/4] wip: more app changes --- .../JsonApi/V1/Posts/PostCollectionQuery.php | 72 ----------------- .../dummy/app/JsonApi/V1/Posts/PostQuery.php | 62 --------------- .../app/JsonApi/V1/Posts/PostRequest.php | 78 ------------------- .../dummy/app/JsonApi/V1/Posts/PostSchema.php | 73 +++++++++++++---- .../dummy/app/JsonApi/V1/Users/UserQuery.php | 45 ----------- .../app/JsonApi/V1/Users/UserRequest.php | 28 ------- .../dummy/app/JsonApi/V1/Users/UserSchema.php | 4 +- .../app/JsonApi/V1/Videos/VideoRequest.php | 33 -------- .../app/JsonApi/V1/Videos/VideoSchema.php | 2 +- 9 files changed, 60 insertions(+), 337 deletions(-) delete mode 100644 tests/dummy/app/JsonApi/V1/Posts/PostCollectionQuery.php delete mode 100644 tests/dummy/app/JsonApi/V1/Posts/PostQuery.php delete mode 100644 tests/dummy/app/JsonApi/V1/Posts/PostRequest.php delete mode 100644 tests/dummy/app/JsonApi/V1/Users/UserQuery.php delete mode 100644 tests/dummy/app/JsonApi/V1/Users/UserRequest.php delete mode 100644 tests/dummy/app/JsonApi/V1/Videos/VideoRequest.php diff --git a/tests/dummy/app/JsonApi/V1/Posts/PostCollectionQuery.php b/tests/dummy/app/JsonApi/V1/Posts/PostCollectionQuery.php deleted file mode 100644 index 24ddaa3..0000000 --- a/tests/dummy/app/JsonApi/V1/Posts/PostCollectionQuery.php +++ /dev/null @@ -1,72 +0,0 @@ - [ - 'nullable', - 'array', - JsonApiRule::fieldSets(), - ], - 'filter' => [ - 'nullable', - 'array', - JsonApiRule::filter(), - ], - 'filter.id' => ['array'], - 'filter.id.*' => ['integer'], - 'filter.published' => [JsonApiRule::boolean()->asString()], - 'filter.slug' => ['string'], - 'include' => [ - 'nullable', - 'string', - JsonApiRule::includePaths(), - ], - 'page' => [ - 'nullable', - 'array', - JsonApiRule::page(), - ], - 'sort' => [ - 'nullable', - 'string', - JsonApiRule::sort(), - ], - 'withCount' => [ - 'nullable', - 'string', - JsonApiRule::countable(), - ], - ]; - } - - /** - * @return void - */ - public function validateResolved() - { - // no-op - } -} diff --git a/tests/dummy/app/JsonApi/V1/Posts/PostQuery.php b/tests/dummy/app/JsonApi/V1/Posts/PostQuery.php deleted file mode 100644 index 6f517a5..0000000 --- a/tests/dummy/app/JsonApi/V1/Posts/PostQuery.php +++ /dev/null @@ -1,62 +0,0 @@ - [ - 'nullable', - 'array', - JsonApiRule::fieldSets(), - ], - 'filter' => [ - 'nullable', - 'array', - JsonApiRule::filter()->forget('id'), - ], - 'filter.published' => ['boolean'], - 'filter.slug' => ['string'], - 'include' => [ - 'nullable', - 'string', - JsonApiRule::includePaths(), - ], - 'page' => JsonApiRule::notSupported(), - 'sort' => JsonApiRule::notSupported(), - 'withCount' => [ - 'nullable', - 'string', - JsonApiRule::countable(), - ], - ]; - } - - /** - * @return void - */ - public function validateResolved() - { - // no-op - } -} diff --git a/tests/dummy/app/JsonApi/V1/Posts/PostRequest.php b/tests/dummy/app/JsonApi/V1/Posts/PostRequest.php deleted file mode 100644 index 6af3214..0000000 --- a/tests/dummy/app/JsonApi/V1/Posts/PostRequest.php +++ /dev/null @@ -1,78 +0,0 @@ -model()) { - $unique->ignore($post); - } - - return [ - 'content' => ['required', 'string'], - 'deletedAt' => ['nullable', JsonApiRule::dateTime()], - 'media' => JsonApiRule::toMany(), - 'slug' => ['required', 'string', $unique], - 'synopsis' => ['required', 'string'], - 'tags' => JsonApiRule::toMany(), - 'title' => ['required', 'string'], - ]; - } - - /** - * @return array - */ - public function deleteRules(): array - { - return [ - 'meta.no_comments' => 'accepted', - ]; - } - - /** - * @return array - */ - public function deleteMessages(): array - { - return [ - 'meta.no_comments.accepted' => 'Cannot delete a post with comments.', - ]; - } - - /** - * @param Post $post - * @return array - */ - public function metaForDelete(Post $post): array - { - return [ - 'no_comments' => $post->comments()->doesntExist(), - ]; - } -} diff --git a/tests/dummy/app/JsonApi/V1/Posts/PostSchema.php b/tests/dummy/app/JsonApi/V1/Posts/PostSchema.php index 958b784..04c8b69 100644 --- a/tests/dummy/app/JsonApi/V1/Posts/PostSchema.php +++ b/tests/dummy/app/JsonApi/V1/Posts/PostSchema.php @@ -12,6 +12,8 @@ namespace App\JsonApi\V1\Posts; use App\Models\Post; +use Illuminate\Http\Request; +use Illuminate\Validation\Rule; use LaravelJsonApi\Core\Schema\Attributes\Model; use LaravelJsonApi\Eloquent\Fields\DateTime; use LaravelJsonApi\Eloquent\Fields\ID; @@ -25,7 +27,6 @@ use LaravelJsonApi\Eloquent\Filters\Scope; use LaravelJsonApi\Eloquent\Filters\Where; use LaravelJsonApi\Eloquent\Filters\WhereIdIn; -use LaravelJsonApi\Eloquent\Pagination\MultiPagination; use LaravelJsonApi\Eloquent\Pagination\PagePagination; use LaravelJsonApi\Eloquent\Schema; use LaravelJsonApi\Eloquent\SoftDeletes; @@ -57,7 +58,7 @@ public function fields(): array ID::make(), BelongsTo::make('author')->type('users')->readOnly(), HasMany::make('comments')->canCount()->readOnly(), - Str::make('content'), + Str::make('content')->rules('required'), DateTime::make('createdAt')->sortable()->readOnly(), SoftDelete::make('deletedAt')->sortable(), MorphToMany::make('media', [ @@ -65,10 +66,13 @@ public function fields(): array BelongsToMany::make('videos'), ])->canCount(), DateTime::make('publishedAt')->sortable(), - Str::make('slug'), - Str::make('synopsis'), + Str::make('slug') + ->rules('required') + ->creationRules(Rule::unique('posts')) + ->updateRules(fn($r, Post $model) => Rule::unique('posts')->ignore($model)), + Str::make('synopsis')->rules('required'), BelongsToMany::make('tags')->canCount()->mustValidate(), - Str::make('title')->sortable(), + Str::make('title')->sortable()->rules('required'), DateTime::make('updatedAt')->sortable()->readOnly(), ]; } @@ -79,9 +83,9 @@ public function fields(): array public function filters(): array { return [ - WhereIdIn::make($this)->delimiter(','), + WhereIdIn::make($this)->delimiter(',')->onlyToMany(), Scope::make('published', 'wherePublished')->asBoolean(), - Where::make('slug')->singular(), + Where::make('slug')->singular()->rules('string'), OnlyTrashed::make('trashed'), ]; } @@ -99,15 +103,52 @@ public function sortables(): iterable /** * @inheritDoc */ - public function pagination(): MultiPagination + public function pagination(): PagePagination { - return new MultiPagination( - PagePagination::make()->withoutNestedMeta(), - PagePagination::make() - ->withoutNestedMeta() - ->withSimplePagination() - ->withPageKey('current-page') - ->withPerPageKey('per-page') - ); + // TODO add validation to the multi-paginator. +// return new MultiPagination( +// PagePagination::make()->withoutNestedMeta(), +// PagePagination::make() +// ->withoutNestedMeta() +// ->withSimplePagination() +// ->withPageKey('current-page') +// ->withPerPageKey('per-page') +// ); + + return PagePagination::make() + ->withoutNestedMeta() + ->withMaxPerPage(200); + } + + /** + * @return array + */ + public function deletionRules(): array + { + return [ + 'meta.no_comments' => 'accepted', + ]; + } + + /** + * @return array + */ + public function deletionMessages(): array + { + return [ + 'meta.no_comments.accepted' => 'Cannot delete a post with comments.', + ]; + } + + /** + * @param Request|null $request + * @param Post $post + * @return array + */ + public function metaForDeletion(?Request $request, Post $post): array + { + return [ + 'no_comments' => $post->comments()->doesntExist(), + ]; } } diff --git a/tests/dummy/app/JsonApi/V1/Users/UserQuery.php b/tests/dummy/app/JsonApi/V1/Users/UserQuery.php deleted file mode 100644 index 228fa6a..0000000 --- a/tests/dummy/app/JsonApi/V1/Users/UserQuery.php +++ /dev/null @@ -1,45 +0,0 @@ - [ - 'nullable', - 'array', - JsonApiRule::fieldSets(), - ], - 'filter' => [ - 'nullable', - 'array', - JsonApiRule::filter()->forget('id'), - ], - 'include' => [ - 'nullable', - 'string', - JsonApiRule::includePaths(), - ], - 'page' => JsonApiRule::notSupported(), - 'sort' => JsonApiRule::notSupported(), - ]; - } -} diff --git a/tests/dummy/app/JsonApi/V1/Users/UserRequest.php b/tests/dummy/app/JsonApi/V1/Users/UserRequest.php deleted file mode 100644 index bc64190..0000000 --- a/tests/dummy/app/JsonApi/V1/Users/UserRequest.php +++ /dev/null @@ -1,28 +0,0 @@ - JsonApiRule::toOne(), - ]; - } -} diff --git a/tests/dummy/app/JsonApi/V1/Users/UserSchema.php b/tests/dummy/app/JsonApi/V1/Users/UserSchema.php index 6502b54..cc59839 100644 --- a/tests/dummy/app/JsonApi/V1/Users/UserSchema.php +++ b/tests/dummy/app/JsonApi/V1/Users/UserSchema.php @@ -45,8 +45,8 @@ public function fields(): array public function filters(): array { return [ - WhereIdIn::make($this)->delimiter(','), - Where::make('email')->singular(), + WhereIdIn::make($this)->delimiter(',')->onlyToMany(), + Where::make('email')->singular()->rules('email'), ]; } diff --git a/tests/dummy/app/JsonApi/V1/Videos/VideoRequest.php b/tests/dummy/app/JsonApi/V1/Videos/VideoRequest.php deleted file mode 100644 index aeb1fce..0000000 --- a/tests/dummy/app/JsonApi/V1/Videos/VideoRequest.php +++ /dev/null @@ -1,33 +0,0 @@ - ['nullable', JsonApiRule::clientId()], - 'tags' => JsonApiRule::toMany(), - 'title' => ['required', 'string'], - 'url' => ['required', 'string'], - ]; - } - -} diff --git a/tests/dummy/app/JsonApi/V1/Videos/VideoSchema.php b/tests/dummy/app/JsonApi/V1/Videos/VideoSchema.php index 3b81da0..ef0c40e 100644 --- a/tests/dummy/app/JsonApi/V1/Videos/VideoSchema.php +++ b/tests/dummy/app/JsonApi/V1/Videos/VideoSchema.php @@ -30,7 +30,7 @@ class VideoSchema extends Schema public function fields(): array { return [ - ID::make()->uuid()->clientIds(), + ID::make()->uuid()->clientIds()->nullable(), DateTime::make('createdAt')->sortable()->readOnly(), BelongsToMany::make('tags')->canCount(), Str::make('title')->sortable(),