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(), pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy