Skip to content

Commit

Permalink
feat(laravel): filter validations rules
Browse files Browse the repository at this point in the history
* refactor(metadata): move parameter validation to the validator component

* feat(laravel): validations rules filters

* cs fixes

* fix(laravel): eloquent filters validation

* fix(laravel): eloquent filters

* fixes

* fix

---------

Co-authored-by: soyuka <[email protected]>
Co-authored-by: Nathan <[email protected]>
  • Loading branch information
3 people authored Sep 16, 2024
1 parent 9d3a0b5 commit a49bde1
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 167 deletions.
60 changes: 32 additions & 28 deletions src/Laravel/ApiPlatformProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
use ApiPlatform\Laravel\Metadata\CachePropertyMetadataFactory;
use ApiPlatform\Laravel\Metadata\CachePropertyNameCollectionMetadataFactory;
use ApiPlatform\Laravel\Metadata\CacheResourceCollectionMetadataFactory;
use ApiPlatform\Laravel\Metadata\ParameterValidationResourceMetadataCollectionFactory;
use ApiPlatform\Laravel\Routing\IriConverter;
use ApiPlatform\Laravel\Routing\Router as UrlGeneratorRouter;
use ApiPlatform\Laravel\Routing\SkolemIriConverter;
Expand Down Expand Up @@ -331,46 +332,49 @@ public function register(): void

return new CacheResourceCollectionMetadataFactory(
new EloquentResourceCollectionMetadataFactory(
new ParameterResourceMetadataCollectionFactory(
$this->app->make(PropertyNameCollectionFactoryInterface::class),
$this->app->make(PropertyMetadataFactoryInterface::class),
new AlternateUriResourceMetadataCollectionFactory(
new FiltersResourceMetadataCollectionFactory(
new FormatsResourceMetadataCollectionFactory(
new InputOutputResourceMetadataCollectionFactory(
new PhpDocResourceMetadataCollectionFactory(
new OperationNameResourceMetadataCollectionFactory(
new LinkResourceMetadataCollectionFactory(
$app->make(LinkFactoryInterface::class),
new UriTemplateResourceMetadataCollectionFactory(
new ParameterValidationResourceMetadataCollectionFactory(
new ParameterResourceMetadataCollectionFactory(
$this->app->make(PropertyNameCollectionFactoryInterface::class),
$this->app->make(PropertyMetadataFactoryInterface::class),
new AlternateUriResourceMetadataCollectionFactory(
new FiltersResourceMetadataCollectionFactory(
new FormatsResourceMetadataCollectionFactory(
new InputOutputResourceMetadataCollectionFactory(
new PhpDocResourceMetadataCollectionFactory(
new OperationNameResourceMetadataCollectionFactory(
new LinkResourceMetadataCollectionFactory(
$app->make(LinkFactoryInterface::class),
$app->make(PathSegmentNameGeneratorInterface::class),
new NotExposedOperationResourceMetadataCollectionFactory(
new UriTemplateResourceMetadataCollectionFactory(
$app->make(LinkFactoryInterface::class),
new AttributesResourceMetadataCollectionFactory(
new ConcernsResourceMetadataCollectionFactory(
null,
$app->make(PathSegmentNameGeneratorInterface::class),
new NotExposedOperationResourceMetadataCollectionFactory(
$app->make(LinkFactoryInterface::class),
new AttributesResourceMetadataCollectionFactory(
new ConcernsResourceMetadataCollectionFactory(
null,
$app->make(LoggerInterface::class),
$config->get('api-platform.defaults', []),
$config->get('api-platform.graphql.enabled'),
),
$app->make(LoggerInterface::class),
$config->get('api-platform.defaults', []),
$config->get('api-platform.graphql.enabled'),
),
$app->make(LoggerInterface::class),
$config->get('api-platform.defaults', []),
$config->get('api-platform.graphql.enabled'),
),
)
)
)
)
)
)
),
$formats,
$config->get('api-platform.patch_formats'),
),
$formats,
$config->get('api-platform.patch_formats'),
)
)
)
),
$app->make('filters'),
$app->make(CamelCaseToSnakeCaseNameConverter::class)
),
$app->make('filters'),
$app->make(CamelCaseToSnakeCaseNameConverter::class)
$app->make('filters')
)
),
true === $config->get('app.debug') ? 'array' : 'file'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Laravel\Metadata;

use ApiPlatform\Metadata\Parameter;
use ApiPlatform\Metadata\Parameters;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
use Illuminate\Validation\Rule;
use Psr\Container\ContainerInterface;

final class ParameterValidationResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
{
public function __construct(
private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null,
private readonly ?ContainerInterface $filterLocator = null,
) {
}

public function create(string $resourceClass): ResourceMetadataCollection
{
$resourceMetadataCollection = $this->decorated?->create($resourceClass) ?? new ResourceMetadataCollection($resourceClass);

foreach ($resourceMetadataCollection as $i => $resource) {
$operations = $resource->getOperations();

foreach ($operations as $operationName => $operation) {
$parameters = $operation->getParameters() ?? new Parameters();
foreach ($parameters as $key => $parameter) {
$parameters->add($key, $this->addSchemaValidation($parameter));
}

if (\count($parameters) > 0) {
$operations->add($operationName, $operation->withParameters($parameters));
}
}

$resourceMetadataCollection[$i] = $resource->withOperations($operations->sort());

if (!$graphQlOperations = $resource->getGraphQlOperations()) {
continue;
}

foreach ($graphQlOperations as $operationName => $operation) {
$parameters = $operation->getParameters() ?? new Parameters();
foreach ($operation->getParameters() ?? [] as $key => $parameter) {
$parameters->add($key, $this->addSchemaValidation($parameter));
}

if (\count($parameters) > 0) {
$graphQlOperations[$operationName] = $operation->withParameters($parameters);
}
}

$resourceMetadataCollection[$i] = $resource->withGraphQlOperations($graphQlOperations);
}

return $resourceMetadataCollection;
}

private function addSchemaValidation(Parameter $parameter): Parameter
{
$schema = $parameter->getSchema();
$required = $parameter->getRequired();
$openApi = $parameter->getOpenApi();

// When it's an array of openapi parameters take the first one as it's probably just a variant of the query parameter,
// only getAllowEmptyValue is used here anyways
if (\is_array($openApi)) {
$openApi = $openApi[0];
}
$assertions = [];
$allowEmptyValue = $openApi?->getAllowEmptyValue();
if ($required || (false === $required && false === $allowEmptyValue)) {
$assertions[] = 'required';
}

if (true === $allowEmptyValue) {
$assertions[] = 'nullable';
}

if (isset($schema['exclusiveMinimum'])) {
$assertions[] = 'gt:'.$schema['exclusiveMinimum'];
}

if (isset($schema['exclusiveMaximum'])) {
$assertions[] = 'lt:'.$schema['exclusiveMaximum'];
}

if (isset($schema['minimum'])) {
$assertions[] = 'gte:'.$schema['minimum'];
}

if (isset($schema['maximum'])) {
$assertions[] = 'lte:'.$schema['maximum'];
}

if (isset($schema['pattern'])) {
$assertions[] = 'regex:'.$schema['pattern'];
}

$minLength = isset($schema['minLength']);
$maxLength = isset($schema['maxLength']);

if ($minLength && $maxLength) {
$assertions[] = \sprintf('between:%s,%s', $schema['minLength'], $schema['maxLength']);
} elseif ($minLength) {
$assertions[] = 'min:'.$schema['minLength'];
} elseif ($maxLength) {
$assertions[] = 'max:'.$schema['maxLength'];
}

$minItems = isset($schema['minItems']);
$maxItems = isset($schema['maxItems']);

if ($minItems && $maxItems) {
$assertions[] = \sprintf('between:%s,%s', $schema['minItems'], $schema['maxItems']);
} elseif ($minItems) {
$assertions[] = 'min:'.$schema['minItems'];
} elseif ($maxItems) {
$assertions[] = 'max:'.$schema['maxItems'];
}

if (isset($schema['multipleOf'])) {
$assertions[] = 'multiple_of:'.$schema['multipleOf'];
}

// if (isset($schema['enum'])) {
// $assertions[] = [Rule::enum($schema['enum'])];
// }

if (isset($schema['type']) && 'array' === $schema['type']) {
$assertions[] = 'array';
}

if (!$assertions) {
return $parameter;
}

if (1 === \count($assertions)) {
return $parameter->withConstraints($assertions[0]);
}

return $parameter->withConstraints($assertions);
}
}
Loading

0 comments on commit a49bde1

Please sign in to comment.